diff options
100 files changed, 1666 insertions, 653 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index d3e80aea6fed..d98e191dbe9e 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -92,6 +92,7 @@ aconfig_declarations_group { "com.android.window.flags.window-aconfig-java", "device_policy_aconfig_flags_lib", "display_flags_lib", + "dropbox_flags_lib", "framework-jobscheduler-job.flags-aconfig-java", "framework_graphics_flags_java_lib", "hwui_flags_java_lib", @@ -1351,3 +1352,19 @@ java_aconfig_library { aconfig_declarations: "backstage_power_flags", defaults: ["framework-minus-apex-aconfig-java-defaults"], } + +// Dropbox data +aconfig_declarations { + name: "dropbox_flags", + package: "com.android.server.feature.flags", + container: "system", + srcs: [ + "services/core/java/com/android/server/feature/dropbox_flags.aconfig", + ], +} + +java_aconfig_library { + name: "dropbox_flags_lib", + aconfig_declarations: "dropbox_flags", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index e39928b5e091..f7e0e9f6a416 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -17,15 +17,14 @@ package android.app; import static android.annotation.Dimension.DP; +import static android.app.Flags.evenlyDividedCallStyleActionLayout; +import static android.app.Flags.updateRankingTime; import static android.app.admin.DevicePolicyResources.Drawables.Source.NOTIFICATION; import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED; import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON; import static android.app.admin.DevicePolicyResources.UNDEFINED; import static android.graphics.drawable.Icon.TYPE_URI; import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP; -import static android.app.Flags.cleanUpSpansAndNewLines; -import static android.app.Flags.evenlyDividedCallStyleActionLayout; -import static android.app.Flags.updateRankingTime; import static java.util.Objects.requireNonNull; diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java index aa3b71a28eba..a12faca71bf6 100644 --- a/core/java/android/app/WindowConfiguration.java +++ b/core/java/android/app/WindowConfiguration.java @@ -100,9 +100,6 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu /** The current windowing mode of the configuration. */ private @WindowingMode int mWindowingMode; - /** The display windowing mode of the configuration */ - private @WindowingMode int mDisplayWindowingMode; - /** Windowing mode is currently not defined. */ public static final int WINDOWING_MODE_UNDEFINED = 0; /** Occupies the full area of the screen or the parent container. */ @@ -193,12 +190,9 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu /** Bit that indicates that the {@link #mRotation} changed. * @hide */ public static final int WINDOW_CONFIG_ROTATION = 1 << 6; - /** Bit that indicates that the {@link #mDisplayWindowingMode} changed. - * @hide */ - public static final int WINDOW_CONFIG_DISPLAY_WINDOWING_MODE = 1 << 7; /** Bit that indicates that the apparent-display changed. * @hide */ - public static final int WINDOW_CONFIG_DISPLAY_ROTATION = 1 << 8; + public static final int WINDOW_CONFIG_DISPLAY_ROTATION = 1 << 7; /** @hide */ @IntDef(flag = true, prefix = { "WINDOW_CONFIG_" }, value = { @@ -209,7 +203,6 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu WINDOW_CONFIG_ACTIVITY_TYPE, WINDOW_CONFIG_ALWAYS_ON_TOP, WINDOW_CONFIG_ROTATION, - WINDOW_CONFIG_DISPLAY_WINDOWING_MODE, WINDOW_CONFIG_DISPLAY_ROTATION, }) public @interface WindowConfig {} @@ -237,7 +230,6 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu dest.writeInt(mActivityType); dest.writeInt(mAlwaysOnTop); dest.writeInt(mRotation); - dest.writeInt(mDisplayWindowingMode); dest.writeInt(mDisplayRotation); } @@ -250,7 +242,6 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu mActivityType = source.readInt(); mAlwaysOnTop = source.readInt(); mRotation = source.readInt(); - mDisplayWindowingMode = source.readInt(); mDisplayRotation = source.readInt(); } @@ -411,17 +402,6 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu return mWindowingMode; } - /** @hide */ - public void setDisplayWindowingMode(@WindowingMode int windowingMode) { - mDisplayWindowingMode = windowingMode; - } - - /** @hide */ - @WindowingMode - public int getDisplayWindowingMode() { - return mDisplayWindowingMode; - } - public void setActivityType(@ActivityType int activityType) { if (mActivityType == activityType) { return; @@ -453,7 +433,6 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu setActivityType(other.mActivityType); setAlwaysOnTop(other.mAlwaysOnTop); setRotation(other.mRotation); - setDisplayWindowingMode(other.mDisplayWindowingMode); } /** Set this object to completely undefined. @@ -472,7 +451,6 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu setActivityType(ACTIVITY_TYPE_UNDEFINED); setAlwaysOnTop(ALWAYS_ON_TOP_UNDEFINED); setRotation(ROTATION_UNDEFINED); - setDisplayWindowingMode(WINDOWING_MODE_UNDEFINED); } /** @hide */ @@ -543,11 +521,6 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu changed |= WINDOW_CONFIG_ROTATION; setRotation(delta.mRotation); } - if (delta.mDisplayWindowingMode != WINDOWING_MODE_UNDEFINED - && mDisplayWindowingMode != delta.mDisplayWindowingMode) { - changed |= WINDOW_CONFIG_DISPLAY_WINDOWING_MODE; - setDisplayWindowingMode(delta.mDisplayWindowingMode); - } if (delta.mDisplayRotation != ROTATION_UNDEFINED && delta.mDisplayRotation != mDisplayRotation) { changed |= WINDOW_CONFIG_DISPLAY_ROTATION; @@ -582,9 +555,6 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu if ((mask & WINDOW_CONFIG_ROTATION) != 0) { setRotation(delta.mRotation); } - if ((mask & WINDOW_CONFIG_DISPLAY_WINDOWING_MODE) != 0) { - setDisplayWindowingMode(delta.mDisplayWindowingMode); - } if ((mask & WINDOW_CONFIG_DISPLAY_ROTATION) != 0) { setDisplayRotation(delta.mDisplayRotation); } @@ -639,11 +609,6 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu changes |= WINDOW_CONFIG_ROTATION; } - if ((compareUndefined || other.mDisplayWindowingMode != WINDOWING_MODE_UNDEFINED) - && mDisplayWindowingMode != other.mDisplayWindowingMode) { - changes |= WINDOW_CONFIG_DISPLAY_WINDOWING_MODE; - } - if ((compareUndefined || other.mDisplayRotation != ROTATION_UNDEFINED) && mDisplayRotation != other.mDisplayRotation) { changes |= WINDOW_CONFIG_DISPLAY_ROTATION; @@ -697,8 +662,6 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu n = mRotation - that.mRotation; if (n != 0) return n; - n = mDisplayWindowingMode - that.mDisplayWindowingMode; - if (n != 0) return n; n = mDisplayRotation - that.mDisplayRotation; if (n != 0) return n; @@ -728,7 +691,6 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu result = 31 * result + mActivityType; result = 31 * result + mAlwaysOnTop; result = 31 * result + mRotation; - result = 31 * result + mDisplayWindowingMode; result = 31 * result + mDisplayRotation; return result; } @@ -742,7 +704,6 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu + " mDisplayRotation=" + (mRotation == ROTATION_UNDEFINED ? "undefined" : rotationToString(mDisplayRotation)) + " mWindowingMode=" + windowingModeToString(mWindowingMode) - + " mDisplayWindowingMode=" + windowingModeToString(mDisplayWindowingMode) + " mActivityType=" + activityTypeToString(mActivityType) + " mAlwaysOnTop=" + alwaysOnTopToString(mAlwaysOnTop) + " mRotation=" + (mRotation == ROTATION_UNDEFINED @@ -818,16 +779,6 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu } /** - * Returns true if the activities associated with this window configuration display a decor - * view. - * @hide - */ - public boolean hasWindowDecorCaption() { - return mActivityType == ACTIVITY_TYPE_STANDARD && (mWindowingMode == WINDOWING_MODE_FREEFORM - || mDisplayWindowingMode == WINDOWING_MODE_FREEFORM); - } - - /** * Returns true if the tasks associated with this window configuration can be resized * independently of their parent container. * @hide diff --git a/core/java/android/os/IHintManager.aidl b/core/java/android/os/IHintManager.aidl index d97ea541f73d..e057a8536fab 100644 --- a/core/java/android/os/IHintManager.aidl +++ b/core/java/android/os/IHintManager.aidl @@ -18,16 +18,21 @@ package android.os; import android.os.IHintSession; +import android.hardware.power.SessionConfig; +import android.hardware.power.SessionTag; /** {@hide} */ interface IHintManager { /** * Creates a {@link Session} for the given set of threads and associates to a binder token. + * Returns a config if creation is not supported, and HMS had to use the + * legacy creation method. */ - IHintSession createHintSession(in IBinder token, in int[] tids, long durationNanos); + IHintSession createHintSessionWithConfig(in IBinder token, in int[] threadIds, + in long durationNanos, in SessionTag tag, out @nullable SessionConfig config); /** - * Get preferred rate limit in nano second. + * Get preferred rate limit in nanoseconds. */ long getHintSessionPreferredRate(); diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java index e6a84df16c27..269839b61bef 100644 --- a/core/java/android/service/autofill/AutofillService.java +++ b/core/java/android/service/autofill/AutofillService.java @@ -37,7 +37,6 @@ import android.view.ViewStructure; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillValue; -import android.view.autofill.IAutoFillManagerClient; import com.android.internal.os.IResultReceiver; @@ -642,7 +641,7 @@ public abstract class AutofillService extends Service { @Override public void onFillCredentialRequest(FillRequest request, IFillCallback callback, - IAutoFillManagerClient autofillClientCallback) { + IBinder autofillClientCallback) { ICancellationSignal transport = CancellationSignal.createTransport(); try { callback.onCancellable(transport); @@ -724,7 +723,7 @@ public abstract class AutofillService extends Service { */ public void onFillCredentialRequest(@NonNull FillRequest request, @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback, - @NonNull IAutoFillManagerClient autofillClientCallback) {} + @NonNull IBinder autofillClientCallback) {} /** * Called by the Android system to convert a credential manager response to a dataset diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl index 2c2feae7aeea..3b64b8a0ec5e 100644 --- a/core/java/android/service/autofill/IAutoFillService.aidl +++ b/core/java/android/service/autofill/IAutoFillService.aidl @@ -16,13 +16,13 @@ package android.service.autofill; +import android.os.IBinder; import android.service.autofill.ConvertCredentialRequest; import android.service.autofill.IConvertCredentialCallback; import android.service.autofill.FillRequest; import android.service.autofill.IFillCallback; import android.service.autofill.ISaveCallback; import android.service.autofill.SaveRequest; -import android.view.autofill.IAutoFillManagerClient; import com.android.internal.os.IResultReceiver; /** @@ -34,7 +34,7 @@ oneway interface IAutoFillService { void onConnectedStateChanged(boolean connected); void onFillRequest(in FillRequest request, in IFillCallback callback); void onFillCredentialRequest(in FillRequest request, in IFillCallback callback, - in IAutoFillManagerClient client); + in IBinder client); void onSaveRequest(in SaveRequest request, in ISaveCallback callback); void onSavedPasswordCountRequest(in IResultReceiver receiver); void onConvertCredentialRequest(in ConvertCredentialRequest convertCredentialRequest, in IConvertCredentialCallback convertCredentialCallback); diff --git a/core/java/android/service/notification/flags.aconfig b/core/java/android/service/notification/flags.aconfig index 35cd3edcafcb..595e41e8d71b 100644 --- a/core/java/android/service/notification/flags.aconfig +++ b/core/java/android/service/notification/flags.aconfig @@ -14,7 +14,17 @@ flag { namespace: "systemui" description: "This flag controls the redacting of sensitive notifications from untrusted NotificationListenerServices" bug: "306271190" +} + +flag { + name: "redact_sensitive_notifications_big_text_style" is_exported: true + namespace: "systemui" + description: "This flag controls the redacting of BigTextStyle fields in sensitive notifications" + bug: "335488909" + metadata { + purpose: PURPOSE_BUGFIX + } } flag { diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java index 5466bf542f91..ebc86ee96f75 100644 --- a/core/java/android/view/TextureView.java +++ b/core/java/android/view/TextureView.java @@ -202,6 +202,14 @@ public class TextureView extends View { // Set by native code, do not write! @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private long mNativeWindow; + // Used for VRR detecting "normal" frame rate rather than "high". This is the previous + // interval for drawing. This can be removed when NORMAL is the default rate for Views. + // (b/329156944) + private long mMinusTwoFrameIntervalMillis = 0; + // Used for VRR detecting "normal" frame rate rather than "high". This is the last + // frame time for drawing. This can be removed when NORMAL is the default rate for Views. + // (b/329156944) + private long mLastFrameTimeMillis = 0; /** * Creates a new TextureView. @@ -890,12 +898,26 @@ public class TextureView extends View { */ @Override protected int calculateFrameRateCategory() { - if (mMinusTwoFrameIntervalMillis > 15 && mMinusOneFrameIntervalMillis > 15) { + long now = getDrawingTime(); + // This isn't necessary when the default frame rate is NORMAL (b/329156944) + if (mMinusTwoFrameIntervalMillis > 15 && (now - mLastFrameTimeMillis) > 15) { return FRAME_RATE_CATEGORY_NORMAL; } return super.calculateFrameRateCategory(); } + /** + * @hide + */ + @Override + protected void votePreferredFrameRate() { + super.votePreferredFrameRate(); + // This isn't necessary when the default frame rate is NORMAL (b/329156944) + long now = getDrawingTime(); + mMinusTwoFrameIntervalMillis = now - mLastFrameTimeMillis; + mLastFrameTimeMillis = now; + } + @UnsupportedAppUsage private final SurfaceTexture.OnFrameAvailableListener mUpdateListener = surfaceTexture -> { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 4c4a22cc96e9..47816f412a62 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -1133,7 +1133,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private static final int FOCUSABLE_MASK = 0x00000011; /** - * This view will adjust its padding to fit sytem windows (e.g. status bar) + * This view will adjust its padding to fit system windows (e.g. status bar) */ private static final int FITS_SYSTEM_WINDOWS = 0x00000002; @@ -5764,23 +5764,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, static final float MAX_FRAME_RATE = 140; - private static final int INFREQUENT_UPDATE_INTERVAL_MILLIS = 100; - private static final int INFREQUENT_UPDATE_COUNTS = 2; - // The preferred frame rate of the view that is mainly used for // touch boosting, view velocity handling, and TextureView. private float mPreferredFrameRate = REQUESTED_FRAME_RATE_CATEGORY_DEFAULT; - private int mInfrequentUpdateCount = 0; - private long mLastUpdateTimeMillis = 0; - /** - * @hide - */ - protected int mMinusOneFrameIntervalMillis = 0; - /** - * @hide - */ - protected int mMinusTwoFrameIntervalMillis = 0; private int mLastFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) @@ -23651,7 +23638,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (sToolkitSetFrameRateReadOnlyFlagValue && sToolkitFrameRateViewEnablingReadOnlyFlagValue) { votePreferredFrameRate(); - updateInfrequentCount(); } mPrivateFlags4 = (mPrivateFlags4 & ~PFLAG4_HAS_MOVED) | PFLAG4_HAS_DRAWN; @@ -32835,6 +32821,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, SENSITIVE_CONTENT_AUTOFILL_HINTS.add(View.AUTOFILL_HINT_USERNAME); SENSITIVE_CONTENT_AUTOFILL_HINTS.add(View.AUTOFILL_HINT_PASSWORD_AUTO); SENSITIVE_CONTENT_AUTOFILL_HINTS.add(View.AUTOFILL_HINT_PASSWORD); + SENSITIVE_CONTENT_AUTOFILL_HINTS.add(View.AUTOFILL_HINT_CREDIT_CARD_NUMBER); + SENSITIVE_CONTENT_AUTOFILL_HINTS.add(View.AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE); + SENSITIVE_CONTENT_AUTOFILL_HINTS.add(View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE); + SENSITIVE_CONTENT_AUTOFILL_HINTS.add(View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY); + SENSITIVE_CONTENT_AUTOFILL_HINTS.add(View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH); + SENSITIVE_CONTENT_AUTOFILL_HINTS.add(View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR); + SENSITIVE_CONTENT_AUTOFILL_HINTS.add(View.AUTOFILL_HINT_CREDENTIAL_MANAGER); } /** @@ -33903,15 +33896,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ protected int calculateFrameRateCategory() { - if (mMinusTwoFrameIntervalMillis + mMinusOneFrameIntervalMillis - < INFREQUENT_UPDATE_INTERVAL_MILLIS) { - return mSizeBasedFrameRateCategoryAndReason; - } - - if (mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS) { - return FRAME_RATE_CATEGORY_NORMAL | FRAME_RATE_CATEGORY_REASON_INTERMITTENT; + int category; + switch (getViewRootImpl().intermittentUpdateState()) { + case ViewRootImpl.INTERMITTENT_STATE_INTERMITTENT -> + category = FRAME_RATE_CATEGORY_NORMAL | FRAME_RATE_CATEGORY_REASON_INTERMITTENT; + case ViewRootImpl.INTERMITTENT_STATE_NOT_INTERMITTENT -> + category = mSizeBasedFrameRateCategoryAndReason; + default -> category = mLastFrameRateCategory; } - return mLastFrameRateCategory; + return category; } /** @@ -33922,76 +33915,99 @@ public class View implements Drawable.Callback, KeyEvent.Callback, protected void votePreferredFrameRate() { // use toolkitSetFrameRate flag to gate the change ViewRootImpl viewRootImpl = getViewRootImpl(); - int width = mRight - mLeft; - int height = mBottom - mTop; - - if (viewRootImpl != null && (width != 0 && height != 0)) { - if (viewRootImpl.shouldCheckFrameRate(mPreferredFrameRate > 0f)) { - float velocityFrameRate = 0f; - if (mAttachInfo.mViewVelocityApi) { - float velocity = mFrameContentVelocity; - - if (velocity < 0f - && (mPrivateFlags4 & (PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN)) == ( - PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN) - && mParent instanceof View - && ((View) mParent).mFrameContentVelocity <= 0 - ) { - // This current calculation is very simple. If something on the screen - // moved, then it votes for the highest velocity. - velocityFrameRate = MAX_FRAME_RATE; - } else if (velocity > 0f) { - velocityFrameRate = convertVelocityToFrameRate(velocity); - } - } - if (velocityFrameRate > 0f || mPreferredFrameRate > 0f) { - int compatibility = FRAME_RATE_COMPATIBILITY_GTE; - float frameRate = velocityFrameRate; - if (mPreferredFrameRate > velocityFrameRate) { - compatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE; - frameRate = mPreferredFrameRate; - } - viewRootImpl.votePreferredFrameRate(frameRate, compatibility); + if (viewRootImpl == null) { + return; // can't vote if not connected + } + float velocity = mFrameContentVelocity; + float frameRate = mPreferredFrameRate; + ViewParent parent = mParent; + if (velocity <= 0 && Float.isNaN(frameRate)) { + // The most common case is when nothing is set, so this special case is called + // often. + if (mAttachInfo.mViewVelocityApi + && (mPrivateFlags4 & (PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN)) == ( + PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN) + && viewRootImpl.shouldCheckFrameRate(false) + && parent instanceof View + && ((View) parent).mFrameContentVelocity <= 0) { + viewRootImpl.votePreferredFrameRate(MAX_FRAME_RATE, FRAME_RATE_COMPATIBILITY_GTE); + } + if (!willNotDraw() && viewRootImpl.shouldCheckFrameRateCategory()) { + int frameRateCategory = calculateFrameRateCategory(); + int category = frameRateCategory & ~FRAME_RATE_CATEGORY_REASON_MASK; + int reason = frameRateCategory & FRAME_RATE_CATEGORY_REASON_MASK; + viewRootImpl.votePreferredFrameRateCategory(category, reason, this); + mLastFrameRateCategory = frameRateCategory; + } + return; + } + if (viewRootImpl.shouldCheckFrameRate(frameRate > 0f)) { + float velocityFrameRate = 0f; + if (mAttachInfo.mViewVelocityApi) { + if (velocity < 0f + && (mPrivateFlags4 & (PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN)) == ( + PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN) + && mParent instanceof View + && ((View) mParent).mFrameContentVelocity <= 0 + ) { + // This current calculation is very simple. If something on the screen + // moved, then it votes for the highest velocity. + velocityFrameRate = MAX_FRAME_RATE; + } else if (velocity > 0f) { + velocityFrameRate = convertVelocityToFrameRate(velocity); } } - if (!willNotDraw() && isDirty() && viewRootImpl.shouldCheckFrameRateCategory()) { - if (sToolkitMetricsForFrameRateDecisionFlagValue) { - float sizePercentage = width * height / mAttachInfo.mDisplayPixelCount; - viewRootImpl.recordViewPercentage(sizePercentage); + if (velocityFrameRate > 0f || frameRate > 0f) { + int compatibility; + if (frameRate >= velocityFrameRate) { + compatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE; + } else { + compatibility = FRAME_RATE_COMPATIBILITY_GTE; + frameRate = velocityFrameRate; } + viewRootImpl.votePreferredFrameRate(frameRate, compatibility); + } + } - int frameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; - if (Float.isNaN(mPreferredFrameRate)) { - frameRateCategory = calculateFrameRateCategory(); - } else if (mPreferredFrameRate < 0) { - switch ((int) mPreferredFrameRate) { - case (int) REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE -> - frameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE - | FRAME_RATE_CATEGORY_REASON_REQUESTED; - case (int) REQUESTED_FRAME_RATE_CATEGORY_LOW -> - frameRateCategory = FRAME_RATE_CATEGORY_LOW - | FRAME_RATE_CATEGORY_REASON_REQUESTED; - case (int) REQUESTED_FRAME_RATE_CATEGORY_NORMAL -> - frameRateCategory = FRAME_RATE_CATEGORY_NORMAL - | FRAME_RATE_CATEGORY_REASON_REQUESTED; - case (int) REQUESTED_FRAME_RATE_CATEGORY_HIGH -> - frameRateCategory = FRAME_RATE_CATEGORY_HIGH - | FRAME_RATE_CATEGORY_REASON_REQUESTED; - default -> { - // invalid frame rate, use default - int category = sToolkitFrameRateDefaultNormalReadOnlyFlagValue - ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH; - frameRateCategory = category - | FRAME_RATE_CATEGORY_REASON_INVALID; - } + if (!willNotDraw() && viewRootImpl.shouldCheckFrameRateCategory()) { + if (sToolkitMetricsForFrameRateDecisionFlagValue) { + int width = mRight - mLeft; + int height = mBottom - mTop; + float sizePercentage = width * height / mAttachInfo.mDisplayPixelCount; + viewRootImpl.recordViewPercentage(sizePercentage); + } + + int frameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; + if (Float.isNaN(frameRate)) { + frameRateCategory = calculateFrameRateCategory(); + } else if (frameRate < 0) { + switch ((int) frameRate) { + case (int) REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE -> + frameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE + | FRAME_RATE_CATEGORY_REASON_REQUESTED; + case (int) REQUESTED_FRAME_RATE_CATEGORY_LOW -> + frameRateCategory = FRAME_RATE_CATEGORY_LOW + | FRAME_RATE_CATEGORY_REASON_REQUESTED; + case (int) REQUESTED_FRAME_RATE_CATEGORY_NORMAL -> + frameRateCategory = FRAME_RATE_CATEGORY_NORMAL + | FRAME_RATE_CATEGORY_REASON_REQUESTED; + case (int) REQUESTED_FRAME_RATE_CATEGORY_HIGH -> + frameRateCategory = FRAME_RATE_CATEGORY_HIGH + | FRAME_RATE_CATEGORY_REASON_REQUESTED; + default -> { + // invalid frame rate, use default + int category = sToolkitFrameRateDefaultNormalReadOnlyFlagValue + ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH; + frameRateCategory = category + | FRAME_RATE_CATEGORY_REASON_INVALID; } } - - int category = frameRateCategory & ~FRAME_RATE_CATEGORY_REASON_MASK; - int reason = frameRateCategory & FRAME_RATE_CATEGORY_REASON_MASK; - viewRootImpl.votePreferredFrameRateCategory(category, reason, this); - mLastFrameRateCategory = frameRateCategory; } + + int category = frameRateCategory & ~FRAME_RATE_CATEGORY_REASON_MASK; + int reason = frameRateCategory & FRAME_RATE_CATEGORY_REASON_MASK; + viewRootImpl.votePreferredFrameRateCategory(category, reason, this); + mLastFrameRateCategory = frameRateCategory; } } @@ -34074,33 +34090,4 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } return 0; } - - /** - * This function is mainly used for migrating infrequent layer logic - * from SurfaceFlinger to Toolkit. - * The infrequent layer logic includes: - * - NORMAL for infrequent update: FT2-FT1 > 100 && FT3-FT2 > 100. - * - HIGH/NORMAL based on size for frequent update: (FT3-FT2) + (FT2 - FT1) < 100. - * - otherwise, use the previous category value. - */ - private void updateInfrequentCount() { - if (!willNotDraw()) { - long currentTimeMillis = getDrawingTime(); - int timeIntervalMillis = - (int) Math.min(Integer.MAX_VALUE, currentTimeMillis - mLastUpdateTimeMillis); - mMinusTwoFrameIntervalMillis = mMinusOneFrameIntervalMillis; - mMinusOneFrameIntervalMillis = timeIntervalMillis; - - mLastUpdateTimeMillis = currentTimeMillis; - if (mMinusTwoFrameIntervalMillis >= 30 && timeIntervalMillis < 2) { - return; - } - if (timeIntervalMillis >= INFREQUENT_UPDATE_INTERVAL_MILLIS) { - mInfrequentUpdateCount = mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS - ? mInfrequentUpdateCount : mInfrequentUpdateCount + 1; - } else { - mInfrequentUpdateCount = 0; - } - } - } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index a8619ab4539c..e2ed2b8097f5 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -392,6 +392,26 @@ public final class ViewRootImpl implements ViewParent, private static final int UNSET_SYNC_ID = -1; + private static final int INFREQUENT_UPDATE_INTERVAL_MILLIS = 100; + private static final int INFREQUENT_UPDATE_COUNTS = 2; + + /** + * The {@link #intermittentUpdateState()} value when the ViewRootImpl isn't intermittent. + */ + public static final int INTERMITTENT_STATE_NOT_INTERMITTENT = 1; + + /** + * The {@link #intermittentUpdateState()} value when the ViewRootImpl is transitioning either + * to or from intermittent to not intermittent. This indicates that the frame rate shouldn't + * change. + */ + public static final int INTERMITTENT_STATE_IN_TRANSITION = -1; + + /** + * The {@link #intermittentUpdateState()} value when the ViewRootImpl is intermittent. + */ + public static final int INTERMITTENT_STATE_INTERMITTENT = 0; + /** * Minimum time to wait before reporting changes to keep clear areas. */ @@ -623,6 +643,15 @@ public final class ViewRootImpl implements ViewParent, // Is the stylus pointer icon enabled private final boolean mIsStylusPointerIconEnabled; + // VRR check for number of infrequent updates + private int mInfrequentUpdateCount = 0; + // VRR time of last update + private long mLastUpdateTimeMillis = 0; + // VRR interval since the previous + private int mMinusOneFrameIntervalMillis = 0; + // VRR interval between the previous and the frame before + private int mMinusTwoFrameIntervalMillis = 0; + /** * Update the Choreographer's FrameInfo object with the timing information for the current * ViewRootImpl instance. Erase the data in the current ViewFrameInfo to prepare for the next @@ -1068,6 +1097,7 @@ public final class ViewRootImpl implements ViewParent, // Used to check if there is a message in the message queue // for idleness handling. private boolean mHasIdledMessage = false; + private boolean mDrawnThisFrame = false; // Used to check if there is a conflict between different frame rate voting. // Take 24 and 30 as an example, 24 is not a divisor of 30. // We consider there is a conflict. @@ -4220,25 +4250,29 @@ public final class ViewRootImpl implements ViewParent, // For the variable refresh rate project. // We set the preferred frame rate and frame rate category at the end of performTraversals // when the values are applicable. - setCategoryFromCategoryCounts(); - setPreferredFrameRate(mPreferredFrameRate); - setPreferredFrameRateCategory(mPreferredFrameRateCategory); - if (!mIsFrameRateConflicted) { - mHandler.removeMessages(MSG_FRAME_RATE_SETTING); - mHandler.sendEmptyMessageDelayed(MSG_FRAME_RATE_SETTING, - FRAME_RATE_SETTING_REEVALUATE_TIME); - } - checkIdleness(); - mFrameRateCategoryHighCount = mFrameRateCategoryHighCount > 0 - ? mFrameRateCategoryHighCount - 1 : mFrameRateCategoryHighCount; - mFrameRateCategoryNormalCount = mFrameRateCategoryNormalCount > 0 - ? mFrameRateCategoryNormalCount - 1 : mFrameRateCategoryNormalCount; - mFrameRateCategoryLowCount = mFrameRateCategoryLowCount > 0 - ? mFrameRateCategoryLowCount - 1 : mFrameRateCategoryLowCount; - mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_DEFAULT; - mPreferredFrameRate = -1; - mIsFrameRateConflicted = false; - mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_UNKNOWN; + if (mDrawnThisFrame) { + mDrawnThisFrame = false; + updateInfrequentCount(); + setCategoryFromCategoryCounts(); + setPreferredFrameRate(mPreferredFrameRate); + setPreferredFrameRateCategory(mPreferredFrameRateCategory); + if (!mIsFrameRateConflicted) { + mHandler.removeMessages(MSG_FRAME_RATE_SETTING); + mHandler.sendEmptyMessageDelayed(MSG_FRAME_RATE_SETTING, + FRAME_RATE_SETTING_REEVALUATE_TIME); + } + checkIdleness(); + mFrameRateCategoryHighCount = mFrameRateCategoryHighCount > 0 + ? mFrameRateCategoryHighCount - 1 : mFrameRateCategoryHighCount; + mFrameRateCategoryNormalCount = mFrameRateCategoryNormalCount > 0 + ? mFrameRateCategoryNormalCount - 1 : mFrameRateCategoryNormalCount; + mFrameRateCategoryLowCount = mFrameRateCategoryLowCount > 0 + ? mFrameRateCategoryLowCount - 1 : mFrameRateCategoryLowCount; + mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_DEFAULT; + mPreferredFrameRate = -1; + mIsFrameRateConflicted = false; + mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_UNKNOWN; + } } private void createSyncIfNeeded() { @@ -12516,6 +12550,15 @@ public final class ViewRootImpl implements ViewParent, * Sets the mPreferredFrameRateCategory from the high, high_hint, normal, and low counts. */ private void setCategoryFromCategoryCounts() { + switch (mPreferredFrameRateCategory) { + case FRAME_RATE_CATEGORY_LOW -> mFrameRateCategoryLowCount = FRAME_RATE_CATEGORY_COUNT; + case FRAME_RATE_CATEGORY_NORMAL -> + mFrameRateCategoryNormalCount = FRAME_RATE_CATEGORY_COUNT; + case FRAME_RATE_CATEGORY_HIGH_HINT -> + mFrameRateCategoryHighHintCount = FRAME_RATE_CATEGORY_COUNT; + case FRAME_RATE_CATEGORY_HIGH -> + mFrameRateCategoryHighCount = FRAME_RATE_CATEGORY_COUNT; + } if (mFrameRateCategoryHighCount > 0) { mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH; } else if (mFrameRateCategoryHighHintCount > 0) { @@ -12661,21 +12704,31 @@ public final class ViewRootImpl implements ViewParent, */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) public void votePreferredFrameRateCategory(int frameRateCategory, int reason, View view) { - switch (frameRateCategory) { - case FRAME_RATE_CATEGORY_LOW -> mFrameRateCategoryLowCount = FRAME_RATE_CATEGORY_COUNT; - case FRAME_RATE_CATEGORY_NORMAL -> - mFrameRateCategoryNormalCount = FRAME_RATE_CATEGORY_COUNT; - case FRAME_RATE_CATEGORY_HIGH_HINT -> - mFrameRateCategoryHighHintCount = FRAME_RATE_CATEGORY_COUNT; - case FRAME_RATE_CATEGORY_HIGH -> - mFrameRateCategoryHighCount = FRAME_RATE_CATEGORY_COUNT; - } if (frameRateCategory > mPreferredFrameRateCategory) { mPreferredFrameRateCategory = frameRateCategory; mFrameRateCategoryChangeReason = reason; - mFrameRateCategoryView = view == null ? "-" : view.getClass().getSimpleName(); +// mFrameRateCategoryView = view == null ? "-" : view.getClass().getSimpleName(); } mHasInvalidation = true; + mDrawnThisFrame = true; + } + + /** + * Returns {@link #INTERMITTENT_STATE_INTERMITTENT} when the ViewRootImpl has only been + * updated intermittently, {@link #INTERMITTENT_STATE_NOT_INTERMITTENT} when it is + * not updated intermittently, and {@link #INTERMITTENT_STATE_IN_TRANSITION} when it + * is transitioning between {@link #INTERMITTENT_STATE_NOT_INTERMITTENT} and + * {@link #INTERMITTENT_STATE_INTERMITTENT}. + */ + int intermittentUpdateState() { + if (mMinusOneFrameIntervalMillis + mMinusTwoFrameIntervalMillis + < INFREQUENT_UPDATE_INTERVAL_MILLIS) { + return INTERMITTENT_STATE_NOT_INTERMITTENT; + } + if (mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS) { + return INTERMITTENT_STATE_INTERMITTENT; + } + return INTERMITTENT_STATE_IN_TRANSITION; } /** @@ -12730,6 +12783,8 @@ public final class ViewRootImpl implements ViewParent, mFrameRateCategoryHighCount = FRAME_RATE_CATEGORY_COUNT; mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_VELOCITY; mFrameRateCategoryView = null; + mHasInvalidation = true; + mDrawnThisFrame = true; return; } } @@ -12761,6 +12816,7 @@ public final class ViewRootImpl implements ViewParent, mPreferredFrameRate = nextFrameRate; mFrameRateCompatibility = nextFrameRateCompatibility; mHasInvalidation = true; + mDrawnThisFrame = true; } /** @@ -12894,4 +12950,29 @@ public final class ViewRootImpl implements ViewParent, mHandler.removeMessages(MSG_CHECK_INVALIDATION_IDLE); mHandler.removeMessages(MSG_FRAME_RATE_SETTING); } + + /** + * This function is mainly used for migrating infrequent layer logic + * from SurfaceFlinger to Toolkit. + * The infrequent layer logic includes: + * - NORMAL for infrequent update: FT2-FT1 > 100 && FT3-FT2 > 100. + * - HIGH/NORMAL based on size for frequent update: (FT3-FT2) + (FT2 - FT1) < 100. + * - otherwise, use the previous category value. + */ + private void updateInfrequentCount() { + long currentTimeMillis = mAttachInfo.mDrawingTime; + int timeIntervalMillis = + (int) Math.min(Integer.MAX_VALUE, currentTimeMillis - mLastUpdateTimeMillis); + mMinusTwoFrameIntervalMillis = mMinusOneFrameIntervalMillis; + mMinusOneFrameIntervalMillis = timeIntervalMillis; + + mLastUpdateTimeMillis = currentTimeMillis; + if (timeIntervalMillis >= INFREQUENT_UPDATE_INTERVAL_MILLIS) { + int infrequentUpdateCount = mInfrequentUpdateCount; + mInfrequentUpdateCount = infrequentUpdateCount == INFREQUENT_UPDATE_COUNTS + ? infrequentUpdateCount : infrequentUpdateCount + 1; + } else { + mInfrequentUpdateCount = 0; + } + } } diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index f31d390d376c..60289c1921c2 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -6436,10 +6436,8 @@ ul.</string> <!-- Communal profile label on a screen. This can be used as a tab label for this profile in tabbed views and can be used to represent the profile in sharing surfaces, etc. [CHAR LIMIT=20] --> <string name="profile_label_communal">Communal</string> - <!-- Notification message used when a notification's normal message contains sensitive information. --> - <!-- TODO b/301960090: replace with redacted message string and action title, when/if UX provides one --> - <!-- DO NOT TRANSLATE --> - <string name="redacted_notification_message"></string> + <!-- Notification message used when a notification's normal message contains sensitive information [CHAR_LIMIT=NOTIF_BODY] --> + <string name="redacted_notification_message">Sensitive notification content hidden</string> <!-- Notification action title used instead of a notification's normal title sensitive [CHAR_LIMIT=NOTIF_BODY] --> <string name="redacted_notification_action_title"></string> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt index 7cb56605cc12..0799fe3b6eb2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt @@ -94,6 +94,8 @@ class CrossActivityBackAnimation @Inject constructor( private var scrimLayer: SurfaceControl? = null private var maxScrimAlpha: Float = 0f + private var isLetterboxed = false + override fun onConfigurationChanged(newConfiguration: Configuration) { cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context) } @@ -112,9 +114,15 @@ class CrossActivityBackAnimation @Inject constructor( initialTouchPos.set(backMotionEvent.touchX, backMotionEvent.touchY) transaction.setAnimationTransaction() - + isLetterboxed = closingTarget!!.taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed + if (isLetterboxed) { + // Include letterbox in back animation + backAnimRect.set(closingTarget!!.windowConfiguration.bounds) + } else { + // otherwise play animation on localBounds only + backAnimRect.set(closingTarget!!.localBounds) + } // Offset start rectangle to align task bounds. - backAnimRect.set(closingTarget!!.localBounds) backAnimRect.offsetTo(0, 0) startClosingRect.set(backAnimRect) @@ -241,6 +249,7 @@ class CrossActivityBackAnimation @Inject constructor( } finishCallback = null removeScrimLayer() + isLetterboxed = false } private fun applyTransform(leash: SurfaceControl?, rect: RectF, alpha: Float) { @@ -274,10 +283,11 @@ class CrossActivityBackAnimation @Inject constructor( scrimLayer = scrimBuilder.build() val colorComponents = floatArrayOf(0f, 0f, 0f) maxScrimAlpha = if (isDarkTheme) MAX_SCRIM_ALPHA_DARK else MAX_SCRIM_ALPHA_LIGHT + val scrimCrop = if (isLetterboxed) backAnimRect else closingTarget!!.localBounds transaction .setColor(scrimLayer, colorComponents) .setAlpha(scrimLayer!!, maxScrimAlpha) - .setCrop(scrimLayer!!, closingTarget!!.localBounds) + .setCrop(scrimLayer!!, scrimCrop) .setRelativeLayer(scrimLayer!!, closingTarget!!.leash, -1) .show(scrimLayer) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java index 7c280994042b..8fb4bdbea933 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java @@ -237,7 +237,8 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract final int letterboxWidth = taskInfo.topActivityLetterboxWidth; // App is not visibly letterboxed if it covers status bar/bottom insets or matches the // stable bounds, so don't show the button - if (stableBounds.height() <= letterboxHeight && stableBounds.width() <= letterboxWidth) { + if (stableBounds.height() <= letterboxHeight && stableBounds.width() <= letterboxWidth + && !taskInfo.isUserFullscreenOverrideEnabled) { return false; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 9adb67c8a65e..2d6ba6ee7217 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -594,7 +594,6 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { .setName("animation-background") .setCallsite("DefaultTransitionHandler") .setColorLayer(); - final SurfaceControl backgroundSurface = colorLayerBuilder.build(); // Attaching the background surface to the transition root could unexpectedly make it // cover one of the split root tasks. To avoid this, put the background surface just @@ -605,8 +604,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { if (isSplitTaskInvolved) { mRootTDAOrganizer.attachToDisplayArea(displayId, colorLayerBuilder); } else { - startTransaction.reparent(backgroundSurface, info.getRootLeash()); + colorLayerBuilder.setParent(info.getRootLeash()); } + + final SurfaceControl backgroundSurface = colorLayerBuilder.build(); startTransaction.setColor(backgroundSurface, colorArray) .setLayer(backgroundSurface, -1) .show(backgroundSurface); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java index 81ba4b37d13b..94e168ed70ed 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java @@ -292,6 +292,24 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { } @Test + public void testUserFullscreenOverrideEnabled_buttonAlwaysShown() { + TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ + true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER); + + final Rect stableBounds = mWindowManager.getTaskStableBounds(); + + // Letterboxed activity that has user fullscreen override should always show button, + // layout should be inflated + taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = stableBounds.height(); + taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = stableBounds.width(); + taskInfo.appCompatTaskInfo.isUserFullscreenOverrideEnabled = true; + + mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true); + + verify(mWindowManager).inflateLayout(); + } + + @Test public void testUpdateDisplayLayout() { final DisplayInfo displayInfo = new DisplayInfo(); displayInfo.logicalWidth = 1000; diff --git a/libs/hwui/SkiaInterpolator.cpp b/libs/hwui/SkiaInterpolator.cpp index c67b135855f7..5a45ad9085e7 100644 --- a/libs/hwui/SkiaInterpolator.cpp +++ b/libs/hwui/SkiaInterpolator.cpp @@ -20,6 +20,7 @@ #include "include/core/SkTypes.h" #include <cstdlib> +#include <cstring> #include <log/log.h> typedef int Dot14; diff --git a/libs/hwui/effects/GainmapRenderer.cpp b/libs/hwui/effects/GainmapRenderer.cpp index 3ebf7d19202d..0a30c6c14c4c 100644 --- a/libs/hwui/effects/GainmapRenderer.cpp +++ b/libs/hwui/effects/GainmapRenderer.cpp @@ -32,6 +32,8 @@ #include "src/core/SkColorFilterPriv.h" #include "src/core/SkImageInfoPriv.h" #include "src/core/SkRuntimeEffectPriv.h" + +#include <cmath> #endif namespace android::uirenderer { @@ -206,12 +208,12 @@ private: void setupGenericUniforms(const sk_sp<const SkImage>& gainmapImage, const SkGainmapInfo& gainmapInfo) { - const SkColor4f logRatioMin({sk_float_log(gainmapInfo.fGainmapRatioMin.fR), - sk_float_log(gainmapInfo.fGainmapRatioMin.fG), - sk_float_log(gainmapInfo.fGainmapRatioMin.fB), 1.f}); - const SkColor4f logRatioMax({sk_float_log(gainmapInfo.fGainmapRatioMax.fR), - sk_float_log(gainmapInfo.fGainmapRatioMax.fG), - sk_float_log(gainmapInfo.fGainmapRatioMax.fB), 1.f}); + const SkColor4f logRatioMin({std::log(gainmapInfo.fGainmapRatioMin.fR), + std::log(gainmapInfo.fGainmapRatioMin.fG), + std::log(gainmapInfo.fGainmapRatioMin.fB), 1.f}); + const SkColor4f logRatioMax({std::log(gainmapInfo.fGainmapRatioMax.fR), + std::log(gainmapInfo.fGainmapRatioMax.fG), + std::log(gainmapInfo.fGainmapRatioMax.fB), 1.f}); const int noGamma = gainmapInfo.fGainmapGamma.fR == 1.f && gainmapInfo.fGainmapGamma.fG == 1.f && gainmapInfo.fGainmapGamma.fB == 1.f; @@ -248,10 +250,10 @@ private: float W = 0.f; if (targetHdrSdrRatio > mGainmapInfo.fDisplayRatioSdr) { if (targetHdrSdrRatio < mGainmapInfo.fDisplayRatioHdr) { - W = (sk_float_log(targetHdrSdrRatio) - - sk_float_log(mGainmapInfo.fDisplayRatioSdr)) / - (sk_float_log(mGainmapInfo.fDisplayRatioHdr) - - sk_float_log(mGainmapInfo.fDisplayRatioSdr)); + W = (std::log(targetHdrSdrRatio) - + std::log(mGainmapInfo.fDisplayRatioSdr)) / + (std::log(mGainmapInfo.fDisplayRatioHdr) - + std::log(mGainmapInfo.fDisplayRatioSdr)); } else { W = 1.f; } diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 66e089627a7b..8bb11badb607 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -1010,7 +1010,15 @@ void CanvasContext::destroyHardwareResources() { } void CanvasContext::onContextDestroyed() { - destroyHardwareResources(); + // We don't want to destroyHardwareResources as that will invalidate display lists which + // the client may not be expecting. Instead just purge all scratch resources + if (mRenderPipeline->isContextReady()) { + freePrefetchedLayers(); + for (const sp<RenderNode>& node : mRenderNodes) { + node->destroyLayers(); + } + mRenderPipeline->onDestroyHardwareResources(); + } } DeferredLayerUpdater* CanvasContext::createTextureLayer() { diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp index f6c57927cc85..6a560b365247 100644 --- a/libs/hwui/utils/Color.cpp +++ b/libs/hwui/utils/Color.cpp @@ -403,7 +403,7 @@ skcms_TransferFunction GetPQSkTransferFunction(float sdr_white_level) { } static skcms_TransferFunction trfn_apply_gain(const skcms_TransferFunction trfn, float gain) { - float pow_gain_ginv = sk_float_pow(gain, 1 / trfn.g); + float pow_gain_ginv = std::pow(gain, 1 / trfn.g); skcms_TransferFunction result; result.g = trfn.g; result.a = trfn.a * pow_gain_ginv; diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp index 882afcab6290..fbb35e2bc355 100644 --- a/native/android/performance_hint.cpp +++ b/native/android/performance_hint.cpp @@ -59,7 +59,8 @@ public: ~APerformanceHintManager() = default; APerformanceHintSession* createSession(const int32_t* threadIds, size_t size, - int64_t initialTargetWorkDurationNanos); + int64_t initialTargetWorkDurationNanos, + hal::SessionTag tag = hal::SessionTag::OTHER); int64_t getPreferredRateNanos() const; private: @@ -84,7 +85,8 @@ struct APerformanceHintSession { public: APerformanceHintSession(std::shared_ptr<IHintManager> hintManager, std::shared_ptr<IHintSession> session, int64_t preferredRateNanos, - int64_t targetDurationNanos); + int64_t targetDurationNanos, + std::optional<hal::SessionConfig> sessionConfig); APerformanceHintSession() = delete; ~APerformanceHintSession(); @@ -116,9 +118,10 @@ private: // Cached samples std::vector<hal::WorkDuration> mActualWorkDurations; std::string mSessionName; - static int32_t sIDCounter; + static int64_t sIDCounter; // The most recent set of thread IDs std::vector<int32_t> mLastThreadIDs; + std::optional<hal::SessionConfig> mSessionConfig; // Tracing helpers void traceThreads(std::vector<int32_t>& tids); void tracePowerEfficient(bool powerEfficient); @@ -129,7 +132,8 @@ private: static std::shared_ptr<IHintManager>* gIHintManagerForTesting = nullptr; static APerformanceHintManager* gHintManagerForTesting = nullptr; -int32_t APerformanceHintSession::sIDCounter = 0; +// Start above the int32 range so we don't collide with config sessions +int64_t APerformanceHintSession::sIDCounter = INT32_MAX; // ===================================== APerformanceHintManager implementation APerformanceHintManager::APerformanceHintManager(std::shared_ptr<IHintManager> manager, @@ -174,16 +178,20 @@ APerformanceHintManager* APerformanceHintManager::create(std::shared_ptr<IHintMa } APerformanceHintSession* APerformanceHintManager::createSession( - const int32_t* threadIds, size_t size, int64_t initialTargetWorkDurationNanos) { + const int32_t* threadIds, size_t size, int64_t initialTargetWorkDurationNanos, + hal::SessionTag tag) { std::vector<int32_t> tids(threadIds, threadIds + size); std::shared_ptr<IHintSession> session; - ndk::ScopedAStatus ret = - mHintManager->createHintSession(mToken, tids, initialTargetWorkDurationNanos, &session); + ndk::ScopedAStatus ret; + std::optional<hal::SessionConfig> sessionConfig; + ret = mHintManager->createHintSessionWithConfig(mToken, tids, initialTargetWorkDurationNanos, + tag, &sessionConfig, &session); + if (!ret.isOk() || !session) { return nullptr; } auto out = new APerformanceHintSession(mHintManager, std::move(session), mPreferredRateNanos, - initialTargetWorkDurationNanos); + initialTargetWorkDurationNanos, sessionConfig); out->traceThreads(tids); out->traceTargetDuration(initialTargetWorkDurationNanos); out->tracePowerEfficient(false); @@ -199,19 +207,23 @@ int64_t APerformanceHintManager::getPreferredRateNanos() const { APerformanceHintSession::APerformanceHintSession(std::shared_ptr<IHintManager> hintManager, std::shared_ptr<IHintSession> session, int64_t preferredRateNanos, - int64_t targetDurationNanos) + int64_t targetDurationNanos, + std::optional<hal::SessionConfig> sessionConfig) : mHintManager(hintManager), mHintSession(std::move(session)), mPreferredRateNanos(preferredRateNanos), mTargetDurationNanos(targetDurationNanos), mFirstTargetMetTimestamp(0), - mLastTargetMetTimestamp(0) { - const std::vector<hal::SessionHint> sessionHintRange{ndk::enum_range<hal::SessionHint>() - .begin(), - ndk::enum_range<hal::SessionHint>().end()}; - - mLastHintSentTimestamp = std::vector<int64_t>(sessionHintRange.size(), 0); - mSessionName = android::base::StringPrintf("ADPF Session %" PRId32, ++sIDCounter); + mLastTargetMetTimestamp(0), + mSessionConfig(sessionConfig) { + if (sessionConfig->id > INT32_MAX) { + ALOGE("Session ID too large, must fit 32-bit integer"); + } + constexpr int numEnums = + ndk::enum_range<hal::SessionHint>().end() - ndk::enum_range<hal::SessionHint>().begin(); + mLastHintSentTimestamp = std::vector<int64_t>(numEnums, 0); + int64_t traceId = sessionConfig.has_value() ? sessionConfig->id : ++sIDCounter; + mSessionName = android::base::StringPrintf("ADPF Session %" PRId64, traceId); } APerformanceHintSession::~APerformanceHintSession() { diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp index bfbe34e7a8a1..974e6e63b424 100644 --- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp +++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp @@ -16,6 +16,8 @@ #define LOG_TAG "PerformanceHintNativeTest" +#include <aidl/android/hardware/power/SessionConfig.h> +#include <aidl/android/hardware/power/SessionTag.h> #include <aidl/android/hardware/power/WorkDuration.h> #include <aidl/android/os/IHintManager.h> #include <android/binder_manager.h> @@ -28,6 +30,8 @@ #include <memory> #include <vector> +using aidl::android::hardware::power::SessionConfig; +using aidl::android::hardware::power::SessionTag; using aidl::android::hardware::power::WorkDuration; using aidl::android::os::IHintManager; using aidl::android::os::IHintSession; @@ -39,8 +43,9 @@ using namespace testing; class MockIHintManager : public IHintManager { public: - MOCK_METHOD(ScopedAStatus, createHintSession, + MOCK_METHOD(ScopedAStatus, createHintSessionWithConfig, (const SpAIBinder& token, const ::std::vector<int32_t>& tids, int64_t durationNanos, + SessionTag tag, std::optional<SessionConfig>* config, std::shared_ptr<IHintSession>* _aidl_return), (override)); MOCK_METHOD(ScopedAStatus, getHintSessionPreferredRate, (int64_t * _aidl_return), (override)); @@ -92,14 +97,18 @@ public: APerformanceHintSession* createSession(APerformanceHintManager* manager, int64_t targetDuration = 56789L) { mMockSession = ndk::SharedRefBase::make<NiceMock<MockIHintSession>>(); - + int64_t sessionId = 123; std::vector<int32_t> tids; tids.push_back(1); tids.push_back(2); - ON_CALL(*mMockIHintManager, createHintSession(_, Eq(tids), Eq(targetDuration), _)) - .WillByDefault(DoAll(SetArgPointee<3>(std::shared_ptr<IHintSession>(mMockSession)), + ON_CALL(*mMockIHintManager, + createHintSessionWithConfig(_, Eq(tids), Eq(targetDuration), _, _, _)) + .WillByDefault(DoAll(SetArgPointee<4>( + std::make_optional<SessionConfig>({.id = sessionId})), + SetArgPointee<5>(std::shared_ptr<IHintSession>(mMockSession)), [] { return ScopedAStatus::ok(); })); + ON_CALL(*mMockIHintManager, setHintSessionThreads(_, _)).WillByDefault([] { return ScopedAStatus::ok(); }); @@ -115,7 +124,6 @@ public: ON_CALL(*mMockSession, reportActualWorkDuration2(_)).WillByDefault([] { return ScopedAStatus::ok(); }); - return APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration); } @@ -178,6 +186,14 @@ TEST_F(PerformanceHintTest, TestSession) { APerformanceHint_closeSession(session); } +TEST_F(PerformanceHintTest, TestUpdatedSessionCreation) { + EXPECT_CALL(*mMockIHintManager, createHintSessionWithConfig(_, _, _, _, _, _)).Times(1); + APerformanceHintManager* manager = createManager(); + APerformanceHintSession* session = createSession(manager); + ASSERT_TRUE(session); + APerformanceHint_closeSession(session); +} + TEST_F(PerformanceHintTest, SetThreads) { APerformanceHintManager* manager = createManager(); diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt index 4109079e20a5..50ebdd5e3ce7 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt @@ -47,9 +47,9 @@ import android.service.autofill.SaveRequest import android.service.credentials.CredentialProviderService import android.util.Log import android.content.Intent +import android.os.IBinder import android.view.autofill.AutofillId import android.view.autofill.AutofillManager -import android.view.autofill.IAutoFillManagerClient import android.widget.RemoteViews import android.widget.inline.InlinePresentationSpec import androidx.autofill.inline.v1.InlineSuggestionUi @@ -95,7 +95,7 @@ class CredentialAutofillService : AutofillService() { request: FillRequest, cancellationSignal: CancellationSignal, callback: FillCallback, - autofillCallback: IAutoFillManagerClient + autofillCallback: IBinder ) { val context = request.fillContexts val structure = context[context.size - 1].structure @@ -160,7 +160,7 @@ class CredentialAutofillService : AutofillService() { CancellationSignal(), Executors.newSingleThreadExecutor(), outcome, - autofillCallback.asBinder() + autofillCallback ) } diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index 563f02d95f3c..c2506d353d14 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -765,7 +765,11 @@ public class Utils { return false; } - final List<UsbPort> usbPortList = context.getSystemService(UsbManager.class).getPorts(); + final UsbManager usbManager = context.getSystemService(UsbManager.class); + if (usbManager == null) { + return false; + } + final List<UsbPort> usbPortList = usbManager.getPorts(); if (usbPortList == null || usbPortList.isEmpty()) { return false; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java index 9df23aa2fe29..a6b1dd3ae578 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java @@ -292,7 +292,6 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { + ", sourceId = " + sourceId); } - updateFallbackActiveDeviceIfNeeded(); } @Override @@ -314,7 +313,18 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { public void onSourceAddFailed( @NonNull BluetoothDevice sink, @NonNull BluetoothLeBroadcastMetadata source, - int reason) {} + int reason) { + if (DEBUG) { + Log.d( + TAG, + "onSourceAddFailed(), sink = " + + sink + + ", reason = " + + reason + + ", source = " + + source); + } + } @Override public void onSourceModified( @@ -369,6 +379,9 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { + ", state = " + state); } + if (BluetoothUtils.isConnected(state)) { + updateFallbackActiveDeviceIfNeeded(); + } } }; @@ -1056,7 +1069,9 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { List<BluetoothLeBroadcastReceiveState> sourceList = mServiceBroadcastAssistant.getAllSources( bluetoothDevice); - return !sourceList.isEmpty(); + return !sourceList.isEmpty() + && sourceList.stream() + .anyMatch(BluetoothUtils::isConnected); }) .collect(Collectors.toList()); if (devicesInSharing.isEmpty()) { @@ -1091,7 +1106,8 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { return; } int fallbackActiveGroupId = getFallbackActiveGroupId(); - if (getGroupId(targetCachedDevice) == fallbackActiveGroupId) { + if (fallbackActiveGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID + && getGroupId(targetCachedDevice) == fallbackActiveGroupId) { Log.d( TAG, "Skip updateFallbackActiveDeviceIfNeeded, already is fallback: " @@ -1101,12 +1117,6 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { targetCachedDevice.setActive(); } - private boolean isDecryptedSource(BluetoothLeBroadcastReceiveState state) { - return state.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED - && state.getBigEncryptionState() - == BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING; - } - private int getFallbackActiveGroupId() { return Settings.Secure.getInt( mContext.getContentResolver(), diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt index d3e4553be209..f0d356c889a9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt @@ -53,6 +53,11 @@ fun SceneScope.MediaCarousel( val mediaFrame = carouselController.mediaFrame (mediaFrame.parent as? ViewGroup)?.removeView(mediaFrame) addView(mediaFrame) + layoutParams = + FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT, + ) } }, update = { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt index fe6701cc8d89..7af9b7bb90e9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt @@ -37,8 +37,6 @@ import androidx.compose.ui.input.pointer.pointerInput import com.android.compose.animation.scene.MutableSceneTransitionLayoutState import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneTransitionLayout -import com.android.compose.animation.scene.UserAction -import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.observableTransitionState import com.android.systemui.ribbon.ui.composable.BottomRightCornerRibbon import com.android.systemui.scene.shared.model.SceneDataSourceDelegator @@ -71,9 +69,7 @@ fun SceneContainer( ) { val coroutineScope = rememberCoroutineScope() val currentSceneKey: SceneKey by viewModel.currentScene.collectAsState() - val currentScene = checkNotNull(sceneByKey[currentSceneKey]) - val currentDestinations: Map<UserAction, UserActionResult> by - currentScene.destinationScenes.collectAsState() + val currentDestinations by viewModel.currentDestinationScenes(coroutineScope).collectAsState() val state: MutableSceneTransitionLayoutState = remember { MutableSceneTransitionLayoutState( initialScene = currentSceneKey, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index a8a1d881b907..f4009ee56737 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -300,14 +300,16 @@ internal fun shouldDrawOrComposeSharedElement( val fromScene = transition.fromScene val toScene = transition.toScene - val chosenByPicker = + val pickedScene = scenePicker.sceneDuringTransition( element = element, transition = transition, fromSceneZIndex = layoutImpl.scenes.getValue(fromScene).zIndex, toSceneZIndex = layoutImpl.scenes.getValue(toScene).zIndex, - ) == scene - return chosenByPicker || transition.currentOverscrollSpec?.scene == scene + ) + ?: return false + + return pickedScene == scene || transition.currentOverscrollSpec?.scene == scene } private fun isSharedElementEnabled( @@ -356,7 +358,9 @@ private fun isElementOpaque( val toState = element.sceneStates[toScene] if (fromState == null && toState == null) { - error("This should not happen, element $element is neither in $fromScene or $toScene") + // TODO(b/311600838): Throw an exception instead once layers of disposed elements are not + // run anymore. + return true } val isSharedElement = fromState != null && toState != null diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt index 418c6bb9af70..7fb5a4d0cc27 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt @@ -59,7 +59,10 @@ internal class Scene( ): Map<UserAction, UserActionResult> { userActions.forEach { (action, result) -> if (key == result.toScene) { - error("Transition to the same scene is not supported. Scene $key, action $action") + error( + "Transition to the same scene is not supported. Scene $key, action $action," + + " result $result" + ) } } return userActions diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt index 2c109a337f65..6bc397e86cfa 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt @@ -205,7 +205,9 @@ interface OverscrollScope { interface ElementScenePicker { /** * Return the scene in which [element] should be drawn (when using `Modifier.element(key)`) or - * composed (when using `MovableElement(key)`) during the given [transition]. + * composed (when using `MovableElement(key)`) during the given [transition]. If this element + * should not be drawn or composed in neither [transition.fromScene] nor [transition.toScene], + * return `null`. * * Important: For [MovableElements][SceneScope.MovableElement], this scene picker will *always* * be used during transitions to decide whether we should compose that element in a given scene @@ -217,12 +219,13 @@ interface ElementScenePicker { transition: TransitionState.Transition, fromSceneZIndex: Float, toSceneZIndex: Float, - ): SceneKey + ): SceneKey? /** * Return [transition.fromScene] if it is in [scenes] and [transition.toScene] is not, or return - * [transition.toScene] if it is in [scenes] and [transition.fromScene] is not, otherwise throw - * an exception (i.e. if neither or both of fromScene and toScene are in [scenes]). + * [transition.toScene] if it is in [scenes] and [transition.fromScene] is not. If neither + * [transition.fromScene] and [transition.toScene] are in [scenes], return `null`. If both + * [transition.fromScene] and [transition.toScene] are in [scenes], throw an exception. * * This function can be useful when computing the scene in which a movable element should be * composed. @@ -231,31 +234,22 @@ interface ElementScenePicker { scenes: Set<SceneKey>, transition: TransitionState.Transition, element: ElementKey, - ): SceneKey { + ): SceneKey? { val fromScene = transition.fromScene val toScene = transition.toScene val fromSceneInScenes = scenes.contains(fromScene) val toSceneInScenes = scenes.contains(toScene) - if (fromSceneInScenes && toSceneInScenes) { - error( - "Element $element can be in both $fromScene and $toScene. You should add a " + - "special case for this transition before calling pickSingleSceneIn()." - ) - } - if (!fromSceneInScenes && !toSceneInScenes) { - error( - "Element $element can be neither in $fromScene and $toScene. This either means " + - "that you should add one of them in the scenes set passed to " + - "pickSingleSceneIn(), or there is an internal error and this element was " + - "composed when it shouldn't be." - ) - } - - return if (fromSceneInScenes) { - fromScene - } else { - toScene + return when { + fromSceneInScenes && toSceneInScenes -> { + error( + "Element $element can be in both $fromScene and $toScene. You should add a " + + "special case for this transition before calling pickSingleSceneIn()." + ) + } + fromSceneInScenes -> fromScene + toSceneInScenes -> toScene + else -> null } } } @@ -312,8 +306,12 @@ class MovableElementScenePicker(private val scenes: Set<SceneKey>) : ElementScen transition: TransitionState.Transition, fromSceneZIndex: Float, toSceneZIndex: Float, - ): SceneKey { - return if (scenes.contains(transition.toScene)) transition.toScene else transition.fromScene + ): SceneKey? { + return when { + scenes.contains(transition.toScene) -> transition.toScene + scenes.contains(transition.fromScene) -> transition.fromScene + else -> null + } } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementScenePickerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementScenePickerTest.kt index fb46a34e3cab..6745fbe064ff 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementScenePickerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementScenePickerTest.kt @@ -38,8 +38,8 @@ class MovableElementScenePickerTest { } @Test - fun toSceneNotInScenes() { - val picker = MovableElementScenePicker(scenes = emptySet()) + fun fromSceneInScenes() { + val picker = MovableElementScenePicker(scenes = setOf(TestScenes.SceneA)) assertThat( picker.sceneDuringTransition( TestElements.Foo, @@ -50,4 +50,18 @@ class MovableElementScenePickerTest { ) .isEqualTo(TestScenes.SceneA) } + + @Test + fun noneInScenes() { + val picker = MovableElementScenePicker(scenes = emptySet()) + assertThat( + picker.sceneDuringTransition( + TestElements.Foo, + transition(from = TestScenes.SceneA, to = TestScenes.SceneB), + fromSceneZIndex = 0f, + toSceneZIndex = 1f, + ) + ) + .isEqualTo(null) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index 3727c113a58e..ab1f458b2f5b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -106,7 +106,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { val destinations by collectLastValue(underTest.destinationScenes) val currentScene by collectLastValue(sceneInteractor.currentScene) - val previousScene by collectLastValue(sceneInteractor.previousScene) + val previousScene by collectLastValue(sceneInteractor.previousScene()) sceneInteractor.changeScene(Scenes.Lockscreen, "reason") sceneInteractor.changeScene(Scenes.QuickSettings, "reason") assertThat(currentScene).isEqualTo(Scenes.QuickSettings) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 65fd1010ec38..a8890078a6f3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -150,6 +150,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { sceneInteractor = sceneInteractor, falsingInteractor = kosmos.falsingInteractor, powerInteractor = kosmos.powerInteractor, + scenes = kosmos.scenes, ) .apply { setTransitionState(transitionState) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt index 63f481695232..871ce6d56e97 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt @@ -296,7 +296,7 @@ class SceneInteractorTest : SysuiTestCase() { fun previousScene() = testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) - val previousScene by collectLastValue(underTest.previousScene) + val previousScene by collectLastValue(underTest.previousScene()) assertThat(previousScene).isNull() val firstScene = currentScene @@ -306,4 +306,19 @@ class SceneInteractorTest : SysuiTestCase() { underTest.changeScene(toScene = Scenes.QuickSettings, "reason") assertThat(previousScene).isEqualTo(Scenes.Shade) } + + @Test + fun previousScene_withIgnoredScene() = + testScope.runTest { + val currentScene by collectLastValue(underTest.currentScene) + val previousScene by collectLastValue(underTest.previousScene(ignored = Scenes.Shade)) + assertThat(previousScene).isNull() + + val firstScene = currentScene + underTest.changeScene(toScene = Scenes.Shade, "reason") + assertThat(previousScene).isEqualTo(firstScene) + + underTest.changeScene(toScene = Scenes.QuickSettings, "reason") + assertThat(previousScene).isNull() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt index ea95aab4a1c4..5c30379ea2fb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt @@ -28,8 +28,10 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.power.data.repository.fakePowerRepository import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.fakeScenes import com.android.systemui.scene.sceneContainerConfig import com.android.systemui.scene.sceneKeys +import com.android.systemui.scene.scenes import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.testKosmos @@ -64,6 +66,7 @@ class SceneContainerViewModelTest : SysuiTestCase() { sceneInteractor = sceneInteractor, falsingInteractor = kosmos.falsingInteractor, powerInteractor = kosmos.powerInteractor, + scenes = kosmos.scenes, ) } @@ -214,4 +217,23 @@ class SceneContainerViewModelTest : SysuiTestCase() { assertThat(isVisible).isFalse() } + + @Test + fun currentDestinationScenes_onlyTheCurrentSceneIsCollected() = + testScope.runTest { + val unused by collectLastValue(underTest.currentDestinationScenes(backgroundScope)) + val currentScene by collectLastValue(sceneInteractor.currentScene) + kosmos.fakeScenes.forEach { scene -> + fakeSceneDataSource.changeScene(toScene = scene.key) + runCurrent() + assertThat(currentScene).isEqualTo(scene.key) + + assertThat(scene.isDestinationScenesBeingCollected).isTrue() + kosmos.fakeScenes + .filter { it.key != scene.key } + .forEach { otherScene -> + assertThat(otherScene.isDestinationScenesBeingCollected).isFalse() + } + } + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt index 26f342aa05ce..468c39daa282 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt @@ -22,7 +22,12 @@ import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes @@ -31,77 +36,93 @@ import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Assume import org.junit.Before -import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) -@Ignore("b/328827631") @EnableSceneContainer class ShadeBackActionInteractorImplTest : SysuiTestCase() { val kosmos = testKosmos() val testScope = kosmos.testScope - val sceneInteractor = kosmos.sceneInteractor - val underTest = kosmos.shadeBackActionInteractor + val sceneInteractor by lazy { kosmos.sceneInteractor } + val shadeInteractor by lazy { kosmos.shadeInteractor } + val fakeAuthenticationRepository by lazy { kosmos.fakeAuthenticationRepository } + val deviceEntryFingerprintAuthRepository by lazy { kosmos.deviceEntryFingerprintAuthRepository } + + lateinit var underTest: ShadeBackActionInteractor @Before - fun ignoreSplitShade() { + fun ignoreSplitShadeAndSetup() { Assume.assumeFalse(Utilities.isLargeScreen(kosmos.applicationContext)) + underTest = kosmos.shadeBackActionInteractor } @Test fun animateCollapseQs_notOnQs() = testScope.runTest { + val actual by collectLastValue(sceneInteractor.currentScene) setScene(Scenes.Shade) underTest.animateCollapseQs(true) runCurrent() - assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Shade) + assertThat(actual).isEqualTo(Scenes.Shade) } @Test fun animateCollapseQs_fullyCollapse_entered() = testScope.runTest { + val actual by collectLastValue(sceneInteractor.currentScene) enterDevice() setScene(Scenes.QuickSettings) underTest.animateCollapseQs(true) runCurrent() - assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone) + assertThat(actual).isEqualTo(Scenes.Gone) } @Test fun animateCollapseQs_fullyCollapse_locked() = testScope.runTest { + val actual by collectLastValue(sceneInteractor.currentScene) setScene(Scenes.QuickSettings) underTest.animateCollapseQs(true) runCurrent() - assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Lockscreen) + assertThat(actual).isEqualTo(Scenes.Lockscreen) } @Test fun animateCollapseQs_notFullyCollapse() = testScope.runTest { + val actual by collectLastValue(sceneInteractor.currentScene) setScene(Scenes.QuickSettings) underTest.animateCollapseQs(false) runCurrent() - assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Shade) + assertThat(actual).isEqualTo(Scenes.Shade) } - private fun enterDevice() { - testScope.runCurrent() + private fun TestScope.enterDevice() { + // configure device unlocked state + fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + runCurrent() + deviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + runCurrent() setScene(Scenes.Gone) } - private fun setScene(key: SceneKey) { + private fun TestScope.setScene(key: SceneKey) { + val actual by collectLastValue(sceneInteractor.currentScene) sceneInteractor.changeScene(key, "test") sceneInteractor.setTransitionState( MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key)) ) - testScope.runCurrent() + runCurrent() + assertThat(actual).isEqualTo(key) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt index 96d1c0de9b66..03a39f8f07d8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt @@ -20,6 +20,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.android.systemui.util.kotlin.BooleanFlowOperators.and @@ -27,6 +28,7 @@ import com.android.systemui.util.kotlin.BooleanFlowOperators.not import com.android.systemui.util.kotlin.BooleanFlowOperators.or import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.junit.Test @@ -62,6 +64,21 @@ class BooleanFlowOperatorsTest : SysuiTestCase() { } @Test + fun and_onlyEmitsWhenValueChanges() = + testScope.runTest { + val flow1 = MutableStateFlow(false) + val flow2 = MutableStateFlow(false) + val values by collectValues(and(flow1, flow2)) + + assertThat(values).containsExactly(false) + flow1.value = true + // Overall value is still false, we should not have emitted again. + assertThat(values).containsExactly(false) + flow2.value = true + assertThat(values).containsExactly(false, true).inOrder() + } + + @Test fun or_allTrue_returnsTrue() = testScope.runTest { val result by collectLastValue(or(TRUE, TRUE)) @@ -83,6 +100,20 @@ class BooleanFlowOperatorsTest : SysuiTestCase() { } @Test + fun or_onlyEmitsWhenValueChanges() = + testScope.runTest { + val flow1 = MutableStateFlow(false) + val flow2 = MutableStateFlow(false) + val values by collectValues(or(flow1, flow2)) + + assertThat(values).containsExactly(false) + flow1.value = true + assertThat(values).containsExactly(false, true).inOrder() + flow2.value = true + assertThat(values).containsExactly(false, true).inOrder() + } + + @Test fun not_true_returnsFalse() = testScope.runTest { val result by collectLastValue(not(TRUE)) diff --git a/packages/SystemUI/res/drawable/notification_material_bg.xml b/packages/SystemUI/res/drawable/notification_material_bg.xml index 587a5a05458f..715be074eaa8 100644 --- a/packages/SystemUI/res/drawable/notification_material_bg.xml +++ b/packages/SystemUI/res/drawable/notification_material_bg.xml @@ -28,9 +28,10 @@ <solid android:color="@color/notification_state_color_default" /> </shape> </item> - <item> + <item android:id="@+id/notification_focus_overlay"> <shape> - <stroke android:width="3dp" android:color="@color/notification_focus_overlay_color"/> + <stroke android:width="@dimen/notification_focus_stroke_width" + android:color="@color/notification_focus_overlay_color"/> </shape> </item> </layer-list>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/media_carousel.xml b/packages/SystemUI/res/layout/media_carousel.xml index 825ece856ed2..ffe269abe08f 100644 --- a/packages/SystemUI/res/layout/media_carousel.xml +++ b/packages/SystemUI/res/layout/media_carousel.xml @@ -19,7 +19,7 @@ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="match_parent" android:clipChildren="false" android:clipToPadding="false" android:forceHasOverlappingRendering="false" @@ -27,7 +27,7 @@ <com.android.systemui.media.controls.ui.view.MediaScrollView android:id="@+id/media_carousel_scroller" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="match_parent" android:scrollbars="none" android:clipChildren="false" android:clipToPadding="false" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 82395e48de20..df57f2a27761 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -310,6 +310,9 @@ <!-- Radius for notifications corners without adjacent notifications --> <dimen name="notification_corner_radius">28dp</dimen> + <!-- Stroke width for notifications focus state overlay, see id/notification_focus_outline --> + <dimen name="notification_focus_stroke_width">3dp</dimen> + <!-- Distance over which notification corner animations run, near the shelf while scrolling. --> <dimen name="notification_corner_animation_distance">48dp</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index fb88f0e4e093..ba869bd56f31 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -204,7 +204,7 @@ public class SystemUIApplication extends Application implements */ public void startSystemUserServicesIfNeeded() { - if (!mProcessWrapper.isSystemUser()) { + if (!shouldStartSystemUserServices()) { Log.wtf(TAG, "Tried starting SystemUser services on non-SystemUser"); return; // Per-user startables are handled in #startSystemUserServicesIfNeeded. } @@ -227,7 +227,7 @@ public class SystemUIApplication extends Application implements * <p>This method must only be called from the main thread.</p> */ void startSecondaryUserServicesIfNeeded() { - if (mProcessWrapper.isSystemUser()) { + if (!shouldStartSecondaryUserServices()) { return; // Per-user startables are handled in #startSystemUserServicesIfNeeded. } // Sort the startables so that we get a deterministic ordering. @@ -238,6 +238,14 @@ public class SystemUIApplication extends Application implements sortedStartables, "StartSecondaryServices", null); } + protected boolean shouldStartSystemUserServices() { + return mProcessWrapper.isSystemUser(); + } + + protected boolean shouldStartSecondaryUserServices() { + return !mProcessWrapper.isSystemUser(); + } + private void startServicesIfNeeded( Map<Class<?>, Provider<CoreStartable>> startables, String metricsPrefix, diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt index dd71bc782c1c..cb458efbdf69 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt @@ -95,7 +95,7 @@ constructor( /** The scene to show when bouncer is dismissed. */ val dismissDestination: Flow<SceneKey> = - sceneInteractor.previousScene.map { it ?: Scenes.Lockscreen } + sceneInteractor.previousScene(Scenes.Bouncer).map { it ?: Scenes.Lockscreen } /** Notifies that the user has places down a pointer, not necessarily dragging just yet. */ fun onDown() { diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt index 71d719df45ff..4ac43bcc4abd 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt @@ -20,6 +20,7 @@ import android.content.ComponentName import android.os.UserHandle import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.TransitionKey import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.widgets.WidgetConfigurator @@ -52,8 +53,8 @@ abstract class BaseCommunalViewModel( communalInteractor.signalUserInteraction() } - fun changeScene(scene: SceneKey) { - communalInteractor.changeScene(scene) + fun changeScene(scene: SceneKey, transitionKey: TransitionKey? = null) { + communalInteractor.changeScene(scene, transitionKey) } /** diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt index 5f4b394a05b9..f20fafccfd19 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt @@ -36,6 +36,7 @@ import com.android.compose.theme.PlatformTheme import com.android.internal.logging.UiEventLogger import com.android.systemui.communal.shared.log.CommunalUiEvent import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.communal.ui.compose.CommunalHub import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent @@ -149,7 +150,10 @@ constructor( private fun onEditDone() { try { - communalViewModel.changeScene(CommunalScenes.Communal) + communalViewModel.changeScene( + CommunalScenes.Communal, + CommunalTransitionKeys.SimpleFade + ) checkNotNull(windowManagerService).lockNow(/* options */ null) finish() } catch (e: RemoteException) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index b8ceab3f0a38..2c869bf8255e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -65,6 +65,7 @@ constructor( ) { override fun start() { + listenForDreamingToAlternateBouncer() listenForDreamingToOccluded() listenForDreamingToGoneWhenDismissable() listenForDreamingToGoneFromBiometricUnlock() @@ -75,6 +76,16 @@ constructor( listenForDreamingToPrimaryBouncer() } + private fun listenForDreamingToAlternateBouncer() { + scope.launch("$TAG#listenForDreamingToAlternateBouncer") { + keyguardInteractor.alternateBouncerShowing + .filterRelevantKeyguardStateAnd { isAlternateBouncerShowing -> + isAlternateBouncerShowing + } + .collect { startTransitionTo(KeyguardState.ALTERNATE_BOUNCER) } + } + } + private fun listenForDreamingToGlanceableHub() { if (!communalHub()) return scope.launch("$TAG#listenForDreamingToGlanceableHub", mainDispatcher) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt index e456a550cad7..2850165b0d1a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt @@ -224,20 +224,12 @@ sealed class TransitionInteractor( ) { if (!KeyguardWmStateRefactor.isEnabled) { scope.launch { - keyguardInteractor.onCameraLaunchDetected - .sample(transitionInteractor.finishedKeyguardState) - .collect { finishedKeyguardState -> - // Other keyguard state transitions may trigger on the first power button - // push, - // so use the last finishedKeyguardState to determine the overriding FROM - // state - if (finishedKeyguardState == fromState) { - startTransitionTo( - toState = KeyguardState.OCCLUDED, - modeOnCanceled = TransitionModeOnCanceled.RESET, - ) - } - } + keyguardInteractor.onCameraLaunchDetected.filterRelevantKeyguardState().collect { + startTransitionTo( + toState = KeyguardState.OCCLUDED, + modeOnCanceled = TransitionModeOnCanceled.RESET, + ) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt index d15d45a477d7..eb716d4c6eef 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt @@ -656,8 +656,14 @@ constructor( if (width == widthInSceneContainerPx && height == heightInSceneContainerPx) { return } + if (width <= 0 || height <= 0) { + // reject as invalid + return + } widthInSceneContainerPx = width heightInSceneContainerPx = height + mediaCarouselScrollHandler.playerWidthPlusPadding = + width + context.resources.getDimensionPixelSize(R.dimen.qs_media_padding) updatePlayers(recreateMedia = true) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt index ab0b0b7b7c6c..2f6919f2fb12 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt @@ -56,7 +56,7 @@ constructor( // TODO(b/330200163) Add an Up from Bottom to be able to collapse the shade // while customizing } else { - sceneInteractor.previousScene.map { previousScene -> + sceneInteractor.previousScene(ignored = Scenes.QuickSettings).map { previousScene -> mapOf( Back to UserActionResult(previousScene ?: Scenes.Shade), Swipe(SwipeDirection.Up) to UserActionResult(previousScene ?: Scenes.Shade), diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt index 02394552e8f9..8ced22223527 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt @@ -140,12 +140,31 @@ constructor( ) /** - * The previous scene. + * The previous scene (or `null` if the previous scene is the [ignored] scene). * * This is effectively the previous value of [currentScene] which means that all caveats, for * example regarding when in a transition the current scene changes, apply. + * + * @param ignored If the previous scene is the same as [ignored], `null` is emitted. This is + * designed to reduce the chances of a scene using [previousScene] naively to then set up a + * user action that ends up leading to itself, which is an illegal operation that would cause + * a crash. */ - val previousScene: StateFlow<SceneKey?> = repository.previousScene + fun previousScene( + ignored: SceneKey? = null, + ): StateFlow<SceneKey?> { + fun SceneKey?.nullifyIfIgnored(): SceneKey? { + return this?.takeIf { this != ignored } + } + + return repository.previousScene + .map { it.nullifyIfIgnored() } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = repository.previousScene.value.nullifyIfIgnored(), + ) + } /** * Returns the keys of all scenes in the container. diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 4774eb32a445..98b4ab88141c 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -253,7 +253,8 @@ constructor( // Track the previous scene (sans Bouncer), so that we know where to go when the device // is unlocked whilst on the bouncer. val previousScene = - sceneInteractor.previousScene + sceneInteractor + .previousScene() .filterNot { it == Scenes.Bouncer } .stateIn(this, SharingStarted.Eagerly, initialValue = null) deviceUnlockedInteractor.deviceUnlockStatus diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt index 231b28424691..ef7829f91723 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt @@ -19,15 +19,22 @@ package com.android.systemui.scene.ui.viewmodel import android.view.MotionEvent import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.UserAction +import com.android.compose.animation.scene.UserActionResult import com.android.systemui.classifier.Classifier import com.android.systemui.classifier.domain.interactor.FalsingInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.model.Scene import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn /** Models UI state for the scene container. */ @SysUISingleton @@ -37,6 +44,7 @@ constructor( private val sceneInteractor: SceneInteractor, private val falsingInteractor: FalsingInteractor, private val powerInteractor: PowerInteractor, + scenes: Set<@JvmSuppressWildcards Scene>, ) { /** * Keys of all scenes in the container. @@ -52,6 +60,23 @@ constructor( /** Whether the container is visible. */ val isVisible: StateFlow<Boolean> = sceneInteractor.isVisible + private val destinationScenesBySceneKey = + scenes.associate { scene -> scene.key to scene.destinationScenes } + + fun currentDestinationScenes( + scope: CoroutineScope, + ): StateFlow<Map<UserAction, UserActionResult>> { + return currentScene + .flatMapLatestConflated { currentSceneKey -> + checkNotNull(destinationScenesBySceneKey[currentSceneKey]) + } + .stateIn( + scope = scope, + started = SharingStarted.WhileSubscribed(), + initialValue = emptyMap(), + ) + } + /** * Binds the given flow so the system remembers it. * diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt index 254c13367ce2..e2dc0928b823 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt @@ -42,6 +42,7 @@ import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHE import com.android.systemui.screenshot.scroll.ScrollCaptureController import com.android.systemui.screenshot.ui.ScreenshotAnimationController import com.android.systemui.screenshot.ui.ScreenshotShelfView +import com.android.systemui.screenshot.ui.SwipeGestureListener import com.android.systemui.screenshot.ui.binder.ScreenshotShelfViewBinder import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel import dagger.assisted.Assisted @@ -75,9 +76,17 @@ constructor( override var isPendingSharedTransition = false private val animationController = ScreenshotAnimationController(view) + private val swipeGestureListener = + SwipeGestureListener( + view, + onDismiss = { requestDismissal(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, it) }, + onCancel = { animationController.getSwipeReturnAnimation().start() } + ) init { - ScreenshotShelfViewBinder.bind(view, viewModel, LayoutInflater.from(context)) + ScreenshotShelfViewBinder.bind(view, viewModel, LayoutInflater.from(context)) { + swipeGestureListener.onMotionEvent(it) + } addPredictiveBackListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) } setOnKeyListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) } debugLog(DEBUG_WINDOW) { "adding OnComputeInternalInsetsListener" } @@ -111,6 +120,10 @@ constructor( override fun setChipIntents(imageData: SavedImageData) {} override fun requestDismissal(event: ScreenshotEvent?) { + requestDismissal(event, getDismissalVelocity()) + } + + private fun requestDismissal(event: ScreenshotEvent?, velocity: Float) { debugLog(DEBUG_DISMISS) { "screenshot dismissal requested: $event" } // If we're already animating out, don't restart the animation @@ -119,7 +132,7 @@ constructor( return } event?.let { logger.log(it, 0, packageName) } - val animator = animationController.getExitAnimation() + val animator = animationController.getSwipeDismissAnimation(velocity) animator.addListener( object : AnimatorListenerAdapter() { override fun onAnimationStart(animator: Animator) { @@ -222,6 +235,12 @@ constructor( ) } + private fun getDismissalVelocity(): Float { + val isLTR = view.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR + // dismiss to the left in LTR locales, to the right in RTL + return if (isLTR) -1.5f else 1.5f + } + @AssistedFactory interface Factory : ScreenshotViewProxy.Factory { override fun getProxy(context: Context, displayId: Int): ScreenshotShelfViewProxy diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt index 2c178736d9c4..f3c421e4896e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt @@ -20,9 +20,12 @@ import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.view.View +import com.android.systemui.res.R +import kotlin.math.abs -class ScreenshotAnimationController(private val view: View) { +class ScreenshotAnimationController(private val view: ScreenshotShelfView) { private var animator: Animator? = null + private val actionContainer = view.requireViewById<View>(R.id.actions_container_background) fun getEntranceAnimation(): Animator { val animator = ValueAnimator.ofFloat(0f, 1f) @@ -41,19 +44,32 @@ class ScreenshotAnimationController(private val view: View) { return animator } - fun getExitAnimation(): Animator { - val animator = ValueAnimator.ofFloat(1f, 0f) - animator.addUpdateListener { view.alpha = it.animatedValue as Float } - animator.addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationStart(animator: Animator) { - view.alpha = 1f - } - override fun onAnimationEnd(animator: Animator) { - view.alpha = 0f - } + fun getSwipeReturnAnimation(): Animator { + animator?.cancel() + val animator = ValueAnimator.ofFloat(view.translationX, 0f) + animator.addUpdateListener { view.translationX = it.animatedValue as Float } + this.animator = animator + return animator + } + + fun getSwipeDismissAnimation(velocity: Float): Animator { + val screenWidth = view.resources.displayMetrics.widthPixels + // translation at which point the visible UI is fully off the screen (in the direction + // according to velocity) + val endX = + if (velocity < 0) { + -1f * actionContainer.right + } else { + (screenWidth - actionContainer.left).toFloat() } - ) + val distance = endX - view.translationX + val animator = ValueAnimator.ofFloat(view.translationX, endX) + animator.addUpdateListener { + view.translationX = it.animatedValue as Float + view.alpha = 1f - it.animatedFraction + } + animator.duration = ((abs(distance / velocity))).toLong() + this.animator = animator return animator } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt index b7a03ef42245..16e23c54dfb2 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt @@ -21,6 +21,7 @@ import android.graphics.Insets import android.graphics.Rect import android.graphics.Region import android.util.AttributeSet +import android.view.MotionEvent import android.view.View import android.widget.ImageView import androidx.constraintlayout.widget.ConstraintLayout @@ -30,6 +31,7 @@ import com.android.systemui.screenshot.FloatingWindowUtil class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) : ConstraintLayout(context, attrs) { lateinit var screenshotPreview: ImageView + var onTouchInterceptListener: ((MotionEvent) -> Boolean)? = null private val displayMetrics = context.resources.displayMetrics private val tmpRect = Rect() @@ -83,4 +85,11 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) : companion object { private const val TOUCH_PADDING_DP = 12f } + + override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { + if (onTouchInterceptListener?.invoke(ev) == true) { + return true + } + return super.onInterceptTouchEvent(ev) + } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/SwipeGestureListener.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/SwipeGestureListener.kt new file mode 100644 index 000000000000..f2889602bc5d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/SwipeGestureListener.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot.ui + +import android.view.MotionEvent +import android.view.VelocityTracker +import android.view.View +import com.android.systemui.screenshot.FloatingWindowUtil +import kotlin.math.abs +import kotlin.math.sign + +class SwipeGestureListener( + private val view: View, + private val onDismiss: (Float) -> Unit, + private val onCancel: () -> Unit +) { + private val velocityTracker = VelocityTracker.obtain() + private val displayMetrics = view.resources.displayMetrics + + private var startX = 0f + + fun onMotionEvent(ev: MotionEvent): Boolean { + ev.offsetLocation(view.translationX, 0f) + when (ev.actionMasked) { + MotionEvent.ACTION_DOWN -> { + velocityTracker.addMovement(ev) + startX = ev.rawX + } + MotionEvent.ACTION_UP -> { + velocityTracker.computeCurrentVelocity(1) + val xVelocity = velocityTracker.xVelocity + if ( + abs(xVelocity) > FloatingWindowUtil.dpToPx(displayMetrics, FLING_THRESHOLD_DP) + ) { + onDismiss.invoke(xVelocity) + return true + } else if ( + abs(view.translationX) > + FloatingWindowUtil.dpToPx(displayMetrics, DISMISS_THRESHOLD_DP) + ) { + onDismiss.invoke(1.5f * sign(view.translationX)) + return true + } else { + velocityTracker.clear() + onCancel.invoke() + } + } + MotionEvent.ACTION_MOVE -> { + velocityTracker.addMovement(ev) + view.translationX = ev.rawX - startX + } + } + return false + } + + companion object { + private const val DISMISS_THRESHOLD_DP = 80f + private const val FLING_THRESHOLD_DP = .8f // dp per ms + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt index 5f835b3697a1..b8b9ce580796 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt @@ -17,8 +17,8 @@ package com.android.systemui.screenshot.ui.binder import android.view.LayoutInflater +import android.view.MotionEvent import android.view.View -import android.view.ViewGroup import android.widget.ImageView import android.widget.LinearLayout import androidx.lifecycle.Lifecycle @@ -26,16 +26,20 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R +import com.android.systemui.screenshot.ui.ScreenshotShelfView import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel import com.android.systemui.util.children import kotlinx.coroutines.launch object ScreenshotShelfViewBinder { fun bind( - view: ViewGroup, + view: ScreenshotShelfView, viewModel: ScreenshotViewModel, layoutInflater: LayoutInflater, + onTouchListener: (MotionEvent) -> Boolean, ) { + view.onTouchInterceptListener = onTouchListener + val previewView: ImageView = view.requireViewById(R.id.screenshot_preview) val previewBorder = view.requireViewById<View>(R.id.screenshot_preview_border) previewView.clipToOutline = true diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt index 8f6b9d0d19e9..a8481cde8ff0 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt @@ -45,6 +45,7 @@ import com.android.systemui.res.R import com.android.systemui.scene.shared.model.SceneDataSourceDelegator import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.phone.SystemUIDialogFactory +import com.android.systemui.util.kotlin.BooleanFlowOperators.or import com.android.systemui.util.kotlin.collectFlow import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -137,7 +138,8 @@ constructor( private var isDreaming = false /** Returns a flow that tracks whether communal hub is available. */ - fun communalAvailable(): Flow<Boolean> = communalInteractor.isCommunalAvailable + fun communalAvailable(): Flow<Boolean> = + or(communalInteractor.isCommunalAvailable, communalInteractor.editModeOpen) /** * Creates the container view containing the glanceable hub UI. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java index ed3a38d3305b..d0db5145e0ff 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java @@ -53,6 +53,8 @@ public class NotificationBackgroundView extends View implements Dumpable { private int mTintColor; @Nullable private Integer mRippleColor; private final float[] mCornerRadii = new float[8]; + private final float[] mFocusOverlayCornerRadii = new float[8]; + private float mFocusOverlayStroke = 0; private boolean mBottomIsRounded; private boolean mBottomAmountClips = true; private int mActualHeight = -1; @@ -74,6 +76,7 @@ public class NotificationBackgroundView extends View implements Dumpable { R.color.notification_state_color_dark); mNormalColor = Utils.getColorAttrDefaultColor(mContext, com.android.internal.R.attr.materialColorSurfaceContainerHigh); + mFocusOverlayStroke = getResources().getDimension(R.dimen.notification_focus_stroke_width); } @Override @@ -290,14 +293,28 @@ public class NotificationBackgroundView extends View implements Dumpable { if (mDontModifyCorners) { return; } - if (mBackground instanceof LayerDrawable) { - int numberOfLayers = ((LayerDrawable) mBackground).getNumberOfLayers(); + if (mBackground instanceof LayerDrawable layerDrawable) { + int numberOfLayers = layerDrawable.getNumberOfLayers(); for (int i = 0; i < numberOfLayers; i++) { - GradientDrawable gradientDrawable = - (GradientDrawable) ((LayerDrawable) mBackground).getDrawable(i); + GradientDrawable gradientDrawable = (GradientDrawable) layerDrawable.getDrawable(i); gradientDrawable.setCornerRadii(mCornerRadii); } + updateFocusOverlayRadii(layerDrawable); + } + } + + private void updateFocusOverlayRadii(LayerDrawable background) { + GradientDrawable overlay = + (GradientDrawable) background.findDrawableByLayerId( + R.id.notification_focus_overlay); + for (int i = 0; i < mCornerRadii.length; i++) { + // in theory subtracting mFocusOverlayStroke/2 should be enough but notification + // background is still peeking a bit from below - probably due to antialiasing or + // overlay uneven scaling. So let's subtract full mFocusOverlayStroke to make sure the + // radius is a bit smaller and covers background corners fully + mFocusOverlayCornerRadii[i] = Math.max(0, mCornerRadii[i] - mFocusOverlayStroke); } + overlay.setCornerRadii(mFocusOverlayCornerRadii); } /** Set the current expand animation size. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 8a1a4f1e4cd8..fd675866fa82 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -482,7 +482,6 @@ public class NotificationStackScrollLayout private Interpolator mHideXInterpolator = Interpolators.FAST_OUT_SLOW_IN; private final NotificationSectionsManager mSectionsManager; - private boolean mAnimateBottomOnLayout; private float mLastSentAppear; private float mLastSentExpandedHeight; private boolean mWillExpand; @@ -2941,23 +2940,11 @@ public class NotificationStackScrollLayout } private void updateFirstAndLastBackgroundViews() { - NotificationSection firstSection = getFirstVisibleSection(); - NotificationSection lastSection = getLastVisibleSection(); - ExpandableView previousFirstChild = - firstSection == null ? null : firstSection.getFirstVisibleChild(); - ExpandableView previousLastChild = - lastSection == null ? null : lastSection.getLastVisibleChild(); - - ExpandableView firstChild = getFirstChildWithBackground(); ExpandableView lastChild = getLastChildWithBackground(); boolean sectionViewsChanged = mSectionsManager.updateFirstAndLastViewsForAllSections( mSections, getChildrenWithBackground()); - if (mAnimationsEnabled && mIsExpanded) { - } else { - } mAmbientState.setLastVisibleBackgroundChild(lastChild); - mAnimateBottomOnLayout = false; invalidate(); } @@ -5465,10 +5452,6 @@ public class NotificationStackScrollLayout } } - void setAnimateBottomOnLayout(boolean animateBottomOnLayout) { - mAnimateBottomOnLayout = animateBottomOnLayout; - } - public void setOnPulseHeightChangedListener(Runnable listener) { mAmbientState.setOnPulseHeightChangedListener(listener); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 5e719b1f0667..d8a16ce5a6e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -306,10 +306,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { }; private final DynamicPrivacyController.Listener mDynamicPrivacyControllerListener = () -> { - if (mView.isExpanded()) { - // The bottom might change because we're using the final actual height of the view - mView.setAnimateBottomOnLayout(true); - } if (!FooterViewRefactor.isEnabled()) { // Let's update the footer once the notifications have been updated (in the next frame) mView.post(this::updateFooter); diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/BooleanFlowOperators.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/BooleanFlowOperators.kt index 693a835e25d2..b3008856d370 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/BooleanFlowOperators.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/BooleanFlowOperators.kt @@ -18,6 +18,7 @@ package com.android.systemui.util.kotlin import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map object BooleanFlowOperators { @@ -31,7 +32,7 @@ object BooleanFlowOperators { * ``` */ fun and(vararg flows: Flow<Boolean>): Flow<Boolean> = - combine(flows.asIterable()) { values -> values.all { it } } + combine(flows.asIterable()) { values -> values.all { it } }.distinctUntilChanged() /** * Logical NOT operator for a boolean flow. @@ -48,5 +49,5 @@ object BooleanFlowOperators { * determine the result. */ fun or(vararg flows: Flow<Boolean>): Flow<Boolean> = - combine(flows.asIterable()) { values -> values.any { it } } + combine(flows.asIterable()) { values -> values.any { it } }.distinctUntilChanged() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt index f534ba5bc68c..8435a03fae9a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt @@ -37,6 +37,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository @@ -160,4 +161,17 @@ class FromDreamingTransitionInteractorTest : SysuiTestCase() { to = KeyguardState.LOCKSCREEN, ) } + + @Test + fun testTransitionToAlternateBouncer() = + testScope.runTest { + kosmos.fakeKeyguardBouncerRepository.setAlternateVisible(true) + runCurrent() + + assertThat(transitionRepository) + .startedTransition( + from = KeyguardState.DREAMING, + to = KeyguardState.ALTERNATE_BOUNCER, + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index 085b70e61339..671b09b257ce 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -755,35 +755,6 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test - fun goneToDreamingLockscreenHosted() = - testScope.runTest { - // GIVEN a device that is not dreaming or dozing - keyguardRepository.setDreamingWithOverlay(false) - keyguardRepository.setDozeTransitionModel( - DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH) - ) - runCurrent() - - // GIVEN a prior transition has run to GONE - runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GONE) - - // WHEN the device begins to dream with the lockscreen hosted dream - keyguardRepository.setDreamingWithOverlay(true) - keyguardRepository.setIsActiveDreamLockscreenHosted(true) - advanceTimeBy(100L) - - assertThat(transitionRepository) - .startedTransition( - to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED, - from = KeyguardState.GONE, - ownerName = "FromGoneTransitionInteractor", - animatorAssertion = { it.isNotNull() } - ) - - coroutineContext.cancelChildren() - } - - @Test fun goneToGlanceableHub() = testScope.runTest { // GIVEN a prior transition has run to GONE @@ -1244,9 +1215,15 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { keyguardRepository.setKeyguardOccluded(true) runCurrent() - // GIVEN device is docked + // GIVEN device is docked/communal is available dockManager.setIsDocked(true) dockManager.setDockEvent(DockManager.STATE_DOCKED) + val idleTransitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(CommunalScenes.Communal) + ) + communalInteractor.setTransitionState(idleTransitionState) + runCurrent() // WHEN occlusion ends keyguardRepository.setKeyguardOccluded(false) @@ -1372,6 +1349,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // WHEN the keyguard is occluded and device wakes up and is no longer dreaming keyguardRepository.setDreaming(false) + testScheduler.advanceTimeBy(150) // The dreaming signal is debounced. + runCurrent() keyguardRepository.setKeyguardOccluded(true) powerInteractor.setAwakeForTest() runCurrent() @@ -1379,7 +1358,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // THEN a transition to OCCLUDED should occur assertThat(transitionRepository) .startedTransition( - ownerName = "FromDreamingTransitionInteractor", + ownerName = "FromDreamingTransitionInteractor(Occluded but no longer dreaming)", from = KeyguardState.DREAMING, to = KeyguardState.OCCLUDED, animatorAssertion = { it.isNotNull() }, @@ -1516,7 +1495,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // THEN a transition to OCCLUDED should occur assertThat(transitionRepository) .startedTransition( - ownerName = "FromAodTransitionInteractor", + ownerName = "FromAodTransitionInteractor(isOccluded = true)", from = KeyguardState.AOD, to = KeyguardState.OCCLUDED, animatorAssertion = { it.isNotNull() }, @@ -1555,7 +1534,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { runTransitionAndSetWakefulness(KeyguardState.AOD, KeyguardState.LOCKSCREEN) runCurrent() - // WHEN the device begins to sleep (first power button press)... + // WHEN the device begins to sleep (first power button press), which starts + // LS -> DOZING... powerInteractor.setAsleepForTest() runCurrent() reset(transitionRepository) @@ -1568,11 +1548,11 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } runCurrent() - // THEN a transition from LOCKSCREEN => OCCLUDED should occur + // THEN a transition from DOZING => OCCLUDED should occur assertThat(transitionRepository) .startedTransition( - ownerName = "FromLockscreenTransitionInteractor", - from = KeyguardState.LOCKSCREEN, + ownerName = "FromDozingTransitionInteractor", + from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED, animatorAssertion = { it.isNotNull() }, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt index a08e4913d553..af785c24bb5c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt @@ -21,6 +21,8 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.FailureMetadata import com.google.common.truth.Subject import com.google.common.truth.Truth @@ -28,6 +30,8 @@ import com.google.common.truth.Truth.assertAbout import junit.framework.Assert.assertEquals import kotlin.test.fail import org.mockito.Mockito +import org.mockito.Mockito.never +import org.mockito.Mockito.verify /** [Subject] used to make assertions about a [Mockito.spy] KeyguardTransitionRepository. */ class KeyguardTransitionRepositorySpySubject @@ -75,34 +79,19 @@ private constructor( animatorAssertion: (Subject) -> Unit, modeOnCanceled: TransitionModeOnCanceled? = null, ) { - // TODO(b/331799060): Remove this workaround once atest supports mocking suspend functions. - Mockito.mockingDetails(repository).invocations.forEach { invocation -> - if (invocation.method.equals(KeyguardTransitionRepository::startTransition.name)) { - val transitionInfo = invocation.arguments.firstOrNull() as TransitionInfo + withArgCaptor<TransitionInfo> { verify(repository).startTransition(capture()) } + .also { transitionInfo -> assertEquals(to, transitionInfo.to) animatorAssertion.invoke(Truth.assertThat(transitionInfo.animator)) from?.let { assertEquals(it, transitionInfo.from) } ownerName?.let { assertEquals(it, transitionInfo.ownerName) } modeOnCanceled?.let { assertEquals(it, transitionInfo.modeOnCanceled) } - invocation.markVerified() } - } } /** Verifies that [KeyguardTransitionRepository.startTransition] was never called. */ suspend fun noTransitionsStarted() { - // TODO(b/331799060): Remove this workaround once atest supports mocking suspend functions. - Mockito.mockingDetails(repository).invocations.forEach { - if ( - it.method.equals(KeyguardTransitionRepository::startTransition.name) && - !it.isVerified - ) { - fail( - "Expected no transitions started, however this transition was started: " + - it.arguments.firstOrNull() - ) - } - } + verify(repository, never()).startTransition(any()) } companion object { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt index ee03236d00b3..fd9daf862190 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt @@ -37,6 +37,7 @@ import com.android.systemui.communal.domain.interactor.setCommunalAvailable import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.communal.util.CommunalColors +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor @@ -325,6 +326,19 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @Test + fun editMode_communalAvailable() = + with(kosmos) { + testScope.runTest { + val available by collectLastValue(underTest.communalAvailable()) + setCommunalAvailable(false) + + assertThat(available).isFalse() + communalInteractor.setEditModeOpen(true) + assertThat(available).isTrue() + } + } + private fun initAndAttachContainerView() { containerView = View(context) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt index 7b73528cd932..efd7f9969d49 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt @@ -22,6 +22,7 @@ import android.app.IUidObserver import android.app.Notification import android.app.PendingIntent import android.app.Person +import android.platform.test.annotations.DisableFlags import android.service.notification.NotificationListenerService.REASON_USER_STOPPED import android.testing.AndroidTestingRunner import android.testing.TestableLooper @@ -193,6 +194,7 @@ class OngoingCallControllerTest : SysuiTestCase() { /** Regression test for b/192379214. */ @Test + @DisableFlags(android.app.Flags.FLAG_UPDATE_RANKING_TIME) fun onEntryUpdated_notificationWhenIsZero_timeHidden() { val notification = NotificationEntryBuilder(createOngoingCallNotifEntry()) notification.modifyNotification(context).setWhen(0) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt index 9ec1481355a5..5f9f6b43be2a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt @@ -43,6 +43,21 @@ class SceneContainerRule : TestRule { SceneContainerFlag.isEnabled ) } + // Get the flag value, treating the unset error as false. + val sceneContainerAconfigEnabled = try { + com.android.systemui.Flags.sceneContainer() + } catch (e: Exception) { + false + } + if (sceneContainerAconfigEnabled) { + Assert.assertTrue( + "FLAG_SCENE_CONTAINER is enabled but SceneContainerFlag.isEnabled" + + " is false. Use `.andSceneContainer()` from" + + " SceneContainerFlagParameterization.kt to parameterize this" + + " flag correctly.", + SceneContainerFlag.isEnabled + ) + } if ( description.hasAnnotation<BrokenWithSceneContainer>() && SceneContainerFlag.isEnabled diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt index 2cdf76d50299..7f6a7bd3f7d8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt @@ -2,6 +2,8 @@ package com.android.systemui.scene import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.shared.model.FakeScene import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.Scenes @@ -16,5 +18,18 @@ var Kosmos.sceneKeys by Fixture { ) } +val Kosmos.fakeScenes by Fixture { + sceneKeys + .map { key -> + FakeScene( + scope = testScope.backgroundScope, + key = key, + ) + } + .toSet() +} + +val Kosmos.scenes by Fixture { fakeScenes } + val Kosmos.initialSceneKey by Fixture { Scenes.Lockscreen } val Kosmos.sceneContainerConfig by Fixture { SceneContainerConfig(sceneKeys, initialSceneKey) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt new file mode 100644 index 000000000000..eeaa9db16730 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.scene.shared.model + +import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.UserAction +import com.android.compose.animation.scene.UserActionResult +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.onCompletion +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.flow.stateIn + +class FakeScene( + val scope: CoroutineScope, + override val key: SceneKey, +) : Scene { + var isDestinationScenesBeingCollected = false + + private val destinationScenesChannel = Channel<Map<UserAction, UserActionResult>>() + + override val destinationScenes = + destinationScenesChannel + .receiveAsFlow() + .onStart { isDestinationScenesBeingCollected = true } + .onCompletion { isDestinationScenesBeingCollected = false } + .stateIn( + scope = scope, + started = SharingStarted.WhileSubscribed(), + initialValue = emptyMap(), + ) + + suspend fun setDestinationScenes(value: Map<UserAction, UserActionResult>) { + destinationScenesChannel.send(value) + } +} diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java index c96688c1b9ae..7ceb3bb56403 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java @@ -28,6 +28,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.os.Handler; +import android.os.IBinder; import android.os.ICancellationSignal; import android.os.RemoteException; import android.service.autofill.AutofillService; @@ -42,7 +43,6 @@ import android.service.autofill.ISaveCallback; import android.service.autofill.SaveRequest; import android.text.format.DateUtils; import android.util.Slog; -import android.view.autofill.IAutoFillManagerClient; import com.android.internal.infra.AbstractRemoteService; import com.android.internal.infra.ServiceConnector; @@ -283,8 +283,7 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { return callback; } - public void onFillCredentialRequest(@NonNull FillRequest request, - IAutoFillManagerClient autofillCallback) { + public void onFillCredentialRequest(@NonNull FillRequest request, IBinder autofillCallback) { if (sVerbose) { Slog.v(TAG, "onFillRequest:" + request); } diff --git a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java index ce9d1803d764..044a06417c00 100644 --- a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java +++ b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java @@ -21,11 +21,11 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.IntentSender; +import android.os.IBinder; import android.service.autofill.ConvertCredentialResponse; import android.service.autofill.FillRequest; import android.service.autofill.FillResponse; import android.util.Slog; -import android.view.autofill.IAutoFillManagerClient; /** * Requests autofill response from a Remote Autofill Service. This autofill service can be @@ -105,8 +105,7 @@ final class SecondaryProviderHandler implements RemoteFillService.FillServiceCal /** * Requests a new fill response. */ - public void onFillRequest(FillRequest pendingFillRequest, int flag, - IAutoFillManagerClient client) { + public void onFillRequest(FillRequest pendingFillRequest, int flag, IBinder client) { Slog.v(TAG, "Requesting fill response to secondary provider."); mLastFlag = flag; if (mRemoteFillService != null && mRemoteFillService.isCredentialAutofillService()) { diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 3a384065217e..cd1ef882868a 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -757,13 +757,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mPendingInlineSuggestionsRequest, id); } mSecondaryProviderHandler.onFillRequest(mPendingFillRequest, - mPendingFillRequest.getFlags(), mClient); + mPendingFillRequest.getFlags(), mClient.asBinder()); } else if (mRemoteFillService != null) { if (mIsPrimaryCredential) { mPendingFillRequest = addCredentialManagerDataToClientState( mPendingFillRequest, mPendingInlineSuggestionsRequest, id); - mRemoteFillService.onFillCredentialRequest(mPendingFillRequest, mClient); + mRemoteFillService.onFillCredentialRequest(mPendingFillRequest, + mClient.asBinder()); } else { mRemoteFillService.onFillRequest(mPendingFillRequest); } @@ -2897,7 +2898,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState + ", clientState=" + newClientState + ", authenticationId=" + authenticationId); } if (Flags.autofillCredmanDevIntegration() && exception != null - && exception instanceof GetCredentialException) { + && !exception.getType().equals(GetCredentialException.TYPE_USER_CANCELED)) { if (dataset != null && dataset.getFieldIds().size() == 1) { if (sDebug) { Slog.d(TAG, "setAuthenticationResultLocked(): result returns with" @@ -6494,21 +6495,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } if (exception != null) { - mClient.onGetCredentialException(id, viewId, exception.getType(), - exception.getMessage()); + if (viewId.isVirtualInt()) { + sendResponseToViewNode(viewId, /*response=*/ null, exception); + } else { + mClient.onGetCredentialException(id, viewId, exception.getType(), + exception.getMessage()); + } } else if (response != null) { if (viewId.isVirtualInt()) { - ViewNode viewNode = getViewNodeFromContextsLocked(viewId); - if (viewNode != null && viewNode.getPendingCredentialCallback() != null) { - Bundle resultData = new Bundle(); - resultData.putParcelable( - CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, - response); - viewNode.getPendingCredentialCallback().send(SUCCESS_CREDMAN_SELECTOR, - resultData); - } else { - Slog.w(TAG, "View node not found after GetCredentialResponse"); - } + sendResponseToViewNode(viewId, response, /*exception=*/ null); } else { mClient.onGetCredentialResponse(id, viewId, response); } @@ -6522,6 +6517,30 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } + @GuardedBy("mLock") + private void sendResponseToViewNode(AutofillId viewId, GetCredentialResponse response, + GetCredentialException exception) { + ViewNode viewNode = getViewNodeFromContextsLocked(viewId); + if (viewNode != null && viewNode.getPendingCredentialCallback() != null) { + Bundle resultData = new Bundle(); + if (response != null) { + resultData.putParcelable( + CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, + response); + viewNode.getPendingCredentialCallback().send(SUCCESS_CREDMAN_SELECTOR, + resultData); + } else if (exception != null) { + resultData.putStringArray( + CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION, + new String[] {exception.getType(), exception.getMessage()}); + viewNode.getPendingCredentialCallback().send(FAILURE_CREDMAN_SELECTOR, + resultData); + } + } else { + Slog.w(TAG, "View node not found after GetCredentialResponse"); + } + } + void autoFillApp(Dataset dataset) { synchronized (mLock) { if (mDestroyed) { diff --git a/services/core/Android.bp b/services/core/Android.bp index e095fa3e8539..300b147509a7 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -234,7 +234,6 @@ java_library_static { "android.hidl.manager-V1.2-java", "cbor-java", "com.android.media.audio-aconfig-java", - "dropbox_flags_lib", "icu4j_calendar_astronomer", "android.security.aaid_aidl-java", "netd-client", diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index c68ef9bd4cce..a7dd2430901f 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -126,7 +126,7 @@ flag { name: "even_dimmer" namespace: "display_manager" description: "Feature flag for extending the brightness below traditional range" - bug: "179428400" + bug: "294760970" is_fixed_read_only: true } diff --git a/services/core/java/com/android/server/feature/Android.bp b/services/core/java/com/android/server/feature/Android.bp deleted file mode 100644 index b0fbab6657b0..000000000000 --- a/services/core/java/com/android/server/feature/Android.bp +++ /dev/null @@ -1,13 +0,0 @@ -aconfig_declarations { - name: "dropbox_flags", - package: "com.android.server.feature.flags", - container: "system", - srcs: [ - "dropbox_flags.aconfig", - ], -} - -java_aconfig_library { - name: "dropbox_flags_lib", - aconfig_declarations: "dropbox_flags", -} diff --git a/services/core/java/com/android/server/media/MediaServerUtils.java b/services/core/java/com/android/server/media/MediaServerUtils.java index 6a954d66d18d..f16d426d634a 100644 --- a/services/core/java/com/android/server/media/MediaServerUtils.java +++ b/services/core/java/com/android/server/media/MediaServerUtils.java @@ -74,10 +74,7 @@ import java.util.List; } final PackageManagerInternal packageManagerInternal = LocalServices.getService(PackageManagerInternal.class); - final int actualUid = - packageManagerInternal.getPackageUid( - packageName, 0 /* flags */, UserHandle.getUserId(uid)); - if (!UserHandle.isSameApp(uid, actualUid)) { + if (!packageManagerInternal.isSameApp(packageName, uid, UserHandle.getUserId(uid))) { String[] uidPackages = context.getPackageManager().getPackagesForUid(uid); throw new IllegalArgumentException( "packageName does not belong to the calling uid; " diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index a3c5d2d336f2..3d6855547bcd 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -66,6 +66,7 @@ import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Bundle; +import android.os.DeadObjectException; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -89,6 +90,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.NoSuchElementException; import java.util.Objects; @@ -189,9 +191,6 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde private final ForegroundServiceDelegationOptions mForegroundServiceDelegationOptions; private final Object mLock = new Object(); - // This field is partially guarded by mLock. Writes and non-atomic iterations (for example: - // index-based-iterations) must be guarded by mLock. But it is safe to acquire an iterator - // without acquiring mLock. private final CopyOnWriteArrayList<ISessionControllerCallbackHolder> mControllerCallbackHolders = new CopyOnWriteArrayList<>(); @@ -887,9 +886,24 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde } playbackState = mPlaybackState; } - performOnCallbackHolders( - "pushPlaybackStateUpdate", - holder -> holder.mCallback.onPlaybackStateChanged(playbackState)); + Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null; + for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) { + try { + holder.mCallback.onPlaybackStateChanged(playbackState); + } catch (DeadObjectException e) { + if (deadCallbackHolders == null) { + deadCallbackHolders = new ArrayList<>(); + } + deadCallbackHolders.add(holder); + logCallbackException("Removing dead callback in pushPlaybackStateUpdate", holder, + e); + } catch (RemoteException e) { + logCallbackException("unexpected exception in pushPlaybackStateUpdate", holder, e); + } + } + if (deadCallbackHolders != null) { + removeControllerHoldersSafely(deadCallbackHolders); + } } private void pushMetadataUpdate() { @@ -900,8 +914,23 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde } metadata = mMetadata; } - performOnCallbackHolders( - "pushMetadataUpdate", holder -> holder.mCallback.onMetadataChanged(metadata)); + Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null; + for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) { + try { + holder.mCallback.onMetadataChanged(metadata); + } catch (DeadObjectException e) { + if (deadCallbackHolders == null) { + deadCallbackHolders = new ArrayList<>(); + } + deadCallbackHolders.add(holder); + logCallbackException("Removing dead callback in pushMetadataUpdate", holder, e); + } catch (RemoteException e) { + logCallbackException("unexpected exception in pushMetadataUpdate", holder, e); + } + } + if (deadCallbackHolders != null) { + removeControllerHoldersSafely(deadCallbackHolders); + } } private void pushQueueUpdate() { @@ -912,18 +941,31 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde } toSend = mQueue == null ? null : new ArrayList<>(mQueue); } - performOnCallbackHolders( - "pushQueueUpdate", - holder -> { - ParceledListSlice<QueueItem> parcelableQueue = null; - if (toSend != null) { - parcelableQueue = new ParceledListSlice<>(toSend); - // Limit the size of initial Parcel to prevent binder buffer overflow - // as onQueueChanged is an async binder call. - parcelableQueue.setInlineCountLimit(1); - } - holder.mCallback.onQueueChanged(parcelableQueue); - }); + Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null; + for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) { + ParceledListSlice<QueueItem> parcelableQueue = null; + if (toSend != null) { + parcelableQueue = new ParceledListSlice<>(toSend); + // Limit the size of initial Parcel to prevent binder buffer overflow + // as onQueueChanged is an async binder call. + parcelableQueue.setInlineCountLimit(1); + } + + try { + holder.mCallback.onQueueChanged(parcelableQueue); + } catch (DeadObjectException e) { + if (deadCallbackHolders == null) { + deadCallbackHolders = new ArrayList<>(); + } + deadCallbackHolders.add(holder); + logCallbackException("Removing dead callback in pushQueueUpdate", holder, e); + } catch (RemoteException e) { + logCallbackException("unexpected exception in pushQueueUpdate", holder, e); + } + } + if (deadCallbackHolders != null) { + removeControllerHoldersSafely(deadCallbackHolders); + } } private void pushQueueTitleUpdate() { @@ -934,8 +976,23 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde } queueTitle = mQueueTitle; } - performOnCallbackHolders( - "pushQueueTitleUpdate", holder -> holder.mCallback.onQueueTitleChanged(queueTitle)); + Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null; + for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) { + try { + holder.mCallback.onQueueTitleChanged(queueTitle); + } catch (DeadObjectException e) { + if (deadCallbackHolders == null) { + deadCallbackHolders = new ArrayList<>(); + } + deadCallbackHolders.add(holder); + logCallbackException("Removing dead callback in pushQueueTitleUpdate", holder, e); + } catch (RemoteException e) { + logCallbackException("unexpected exception in pushQueueTitleUpdate", holder, e); + } + } + if (deadCallbackHolders != null) { + removeControllerHoldersSafely(deadCallbackHolders); + } } private void pushExtrasUpdate() { @@ -946,8 +1003,23 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde } extras = mExtras; } - performOnCallbackHolders( - "pushExtrasUpdate", holder -> holder.mCallback.onExtrasChanged(extras)); + Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null; + for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) { + try { + holder.mCallback.onExtrasChanged(extras); + } catch (DeadObjectException e) { + if (deadCallbackHolders == null) { + deadCallbackHolders = new ArrayList<>(); + } + deadCallbackHolders.add(holder); + logCallbackException("Removing dead callback in pushExtrasUpdate", holder, e); + } catch (RemoteException e) { + logCallbackException("unexpected exception in pushExtrasUpdate", holder, e); + } + } + if (deadCallbackHolders != null) { + removeControllerHoldersSafely(deadCallbackHolders); + } } private void pushVolumeUpdate() { @@ -958,8 +1030,23 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde } info = getVolumeAttributes(); } - performOnCallbackHolders( - "pushVolumeUpdate", holder -> holder.mCallback.onVolumeInfoChanged(info)); + Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null; + for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) { + try { + holder.mCallback.onVolumeInfoChanged(info); + } catch (DeadObjectException e) { + if (deadCallbackHolders == null) { + deadCallbackHolders = new ArrayList<>(); + } + deadCallbackHolders.add(holder); + logCallbackException("Removing dead callback in pushVolumeUpdate", holder, e); + } catch (RemoteException e) { + logCallbackException("unexpected exception in pushVolumeUpdate", holder, e); + } + } + if (deadCallbackHolders != null) { + removeControllerHoldersSafely(deadCallbackHolders); + } } private void pushEvent(String event, Bundle data) { @@ -968,7 +1055,23 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde return; } } - performOnCallbackHolders("pushEvent", holder -> holder.mCallback.onEvent(event, data)); + Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null; + for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) { + try { + holder.mCallback.onEvent(event, data); + } catch (DeadObjectException e) { + if (deadCallbackHolders == null) { + deadCallbackHolders = new ArrayList<>(); + } + deadCallbackHolders.add(holder); + logCallbackException("Removing dead callback in pushEvent", holder, e); + } catch (RemoteException e) { + logCallbackException("unexpected exception in pushEvent", holder, e); + } + } + if (deadCallbackHolders != null) { + removeControllerHoldersSafely(deadCallbackHolders); + } } private void pushSessionDestroyed() { @@ -979,37 +1082,20 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde return; } } - performOnCallbackHolders( - "pushSessionDestroyed", - holder -> { - holder.mCallback.asBinder().unlinkToDeath(holder.mDeathMonitor, 0); - holder.mCallback.onSessionDestroyed(); - }); - // After notifying clear all listeners - synchronized (mLock) { - mControllerCallbackHolders.clear(); - } - } - - private interface ControllerCallbackCall { - - void performOn(ISessionControllerCallbackHolder holder) throws RemoteException; - } - - private void performOnCallbackHolders(String operationName, ControllerCallbackCall call) { - ArrayList<ISessionControllerCallbackHolder> deadCallbackHolders = new ArrayList<>(); for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) { try { - call.performOn(holder); - } catch (RemoteException | NoSuchElementException exception) { - deadCallbackHolders.add(holder); - logCallbackException( - "Exception while executing: " + operationName, holder, exception); + holder.mCallback.asBinder().unlinkToDeath(holder.mDeathMonitor, 0); + holder.mCallback.onSessionDestroyed(); + } catch (NoSuchElementException e) { + logCallbackException("error unlinking to binder death", holder, e); + } catch (DeadObjectException e) { + logCallbackException("Removing dead callback in pushSessionDestroyed", holder, e); + } catch (RemoteException e) { + logCallbackException("unexpected exception in pushSessionDestroyed", holder, e); } } - synchronized (mLock) { - mControllerCallbackHolders.removeAll(deadCallbackHolders); - } + // After notifying clear all listeners + removeControllerHoldersSafely(null); } private PlaybackState getStateWithUpdatedPosition() { @@ -1057,6 +1143,17 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde return -1; } + private void removeControllerHoldersSafely( + Collection<ISessionControllerCallbackHolder> holders) { + synchronized (mLock) { + if (holders == null) { + mControllerCallbackHolders.clear(); + } else { + mControllerCallbackHolders.removeAll(holders); + } + } + } + private PlaybackInfo getVolumeAttributes() { int volumeType; AudioAttributes attributes; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index ebea05ddfd0c..0c658379fa4d 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -98,6 +98,7 @@ import static android.os.UserHandle.USER_NULL; import static android.os.UserHandle.USER_SYSTEM; import static android.service.notification.Flags.callstyleCallbackApi; import static android.service.notification.Flags.redactSensitiveNotificationsFromUntrustedListeners; +import static android.service.notification.Flags.redactSensitiveNotificationsBigTextStyle; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING; @@ -139,6 +140,7 @@ import static android.service.notification.NotificationListenerService.TRIM_FULL import static android.service.notification.NotificationListenerService.TRIM_LIGHT; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.contentprotection.flags.Flags.rapidClearNotificationsByListenerAppOpEnabled; + import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES; @@ -306,6 +308,7 @@ import android.view.Display; import android.view.accessibility.AccessibilityManager; import android.widget.RemoteViews; import android.widget.Toast; + import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -357,7 +360,9 @@ import com.android.server.utils.quota.MultiRateLimiter; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.BackgroundActivityStartCallback; import com.android.server.wm.WindowManagerInternal; + import libcore.io.IoUtils; + import org.json.JSONException; import org.json.JSONObject; import org.xmlpull.v1.XmlPullParserException; @@ -11775,10 +11780,18 @@ public class NotificationManagerService extends SystemService { } if (lifetimeExtensionRefactor()) { + if (sendRedacted && redactedSbn == null) { + redactedSbn = redactStatusBarNotification(sbn); + redactedCache = new TrimCache(redactedSbn); + } + final StatusBarNotification sbnToPost = sendRedacted + ? redactedCache.ForListener(info) : trimCache.ForListener(info); + // Checks if this is a request to notify system UI about a notification that // has been lifetime extended. // (We only need to check old for the flag, because in both cancellation and - // update cases, old should have the flag.) + // update cases, old should have the flag, whereas in update cases the + // new will NOT have the flag.) // If it is such a request, and this is system UI, we send the post request // only to System UI, and break as we don't need to continue checking other // Managed Services. @@ -11786,7 +11799,7 @@ public class NotificationManagerService extends SystemService { && (old.getNotification().flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0) { final NotificationRankingUpdate update = makeRankingUpdateLocked(info); - listenerCalls.add(() -> notifyPosted(info, oldSbn, update)); + listenerCalls.add(() -> notifyPosted(info, sbnToPost, update)); break; } } @@ -11917,6 +11930,14 @@ public class NotificationManagerService extends SystemService { redactedText, System.currentTimeMillis(), empty)); redactedNotifBuilder.setStyle(messageStyle); } + if (redactSensitiveNotificationsBigTextStyle() + && oldNotif.isStyle(Notification.BigTextStyle.class)) { + Notification.BigTextStyle bigTextStyle = new Notification.BigTextStyle(); + bigTextStyle.bigText(mContext.getString(R.string.redacted_notification_message)); + bigTextStyle.setBigContentTitle(""); + bigTextStyle.setSummaryText(""); + redactedNotifBuilder.setStyle(bigTextStyle); + } Notification redacted = redactedNotifBuilder.build(); // Notification extras can't always be overridden by a builder (configured by a system diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index c69bead309f1..8dd3e52c1039 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -66,15 +66,16 @@ import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; -import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import android.widget.RemoteViews; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.server.EventLogTags; import com.android.server.LocalServices; import com.android.server.uri.UriGrantsManagerInternal; + import dalvik.annotation.optimization.NeverCompile; import java.io.PrintWriter; diff --git a/services/core/java/com/android/server/pm/PackageManagerNative.java b/services/core/java/com/android/server/pm/PackageManagerNative.java index d035084d8b02..66ecd6e67e56 100644 --- a/services/core/java/com/android/server/pm/PackageManagerNative.java +++ b/services/core/java/com/android/server/pm/PackageManagerNative.java @@ -68,6 +68,11 @@ final class PackageManagerNative extends IPackageManagerNative.Stub { } } + @Override + public int getPackageUid(String packageName, long flags, int userId) throws RemoteException { + return mPm.snapshotComputer().getPackageUid(packageName, flags, userId); + } + // NB: this differentiates between preloads and sideloads @Override public String getInstallerForPackage(String packageName) throws RemoteException { diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java index 101983e6ecf1..6e17fd3d8c87 100644 --- a/services/core/java/com/android/server/power/hint/HintManagerService.java +++ b/services/core/java/com/android/server/power/hint/HintManagerService.java @@ -20,11 +20,14 @@ import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import static com.android.server.power.hint.Flags.powerhintThreadCleanup; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.StatsManager; import android.app.UidObserver; import android.content.Context; +import android.hardware.power.SessionConfig; +import android.hardware.power.SessionTag; import android.hardware.power.WorkDuration; import android.os.Binder; import android.os.Handler; @@ -67,6 +70,7 @@ import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; /** An hint service implementation that runs in System Server process. */ public final class HintManagerService extends SystemService { @@ -87,7 +91,7 @@ public final class HintManagerService extends SystemService { @GuardedBy("mLock") private final ArrayMap<Integer, ArrayMap<IBinder, ArraySet<AppHintSession>>> mActiveSessions; - /** Lock to protect HAL handles and listen list. */ + /** Lock to protect mActiveSessions. */ private final Object mLock = new Object(); @GuardedBy("mNonIsolatedTidsLock") @@ -104,6 +108,8 @@ public final class HintManagerService extends SystemService { private final Context mContext; + private AtomicBoolean mConfigCreationSupport = new AtomicBoolean(true); + private static final String PROPERTY_SF_ENABLE_CPU_HINT = "debug.sf.enable_adpf_cpu_hint"; private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager"; @@ -217,6 +223,9 @@ public final class HintManagerService extends SystemService { private static native long nativeCreateHintSession(int tgid, int uid, int[] tids, long durationNanos); + private static native long nativeCreateHintSessionWithConfig(int tgid, int uid, int[] tids, + long durationNanos, int tag, SessionConfig config); + private static native void nativePauseHintSession(long halPtr); private static native void nativeResumeHintSession(long halPtr); @@ -253,6 +262,12 @@ public final class HintManagerService extends SystemService { return nativeCreateHintSession(tgid, uid, tids, durationNanos); } + /** Wrapper for HintManager.nativeCreateHintSessionWithConfig */ + public long halCreateHintSessionWithConfig( + int tgid, int uid, int[] tids, long durationNanos, int tag, SessionConfig config) { + return nativeCreateHintSessionWithConfig(tgid, uid, tids, durationNanos, tag, config); + } + /** Wrapper for HintManager.nativePauseHintSession */ public void halPauseHintSession(long halPtr) { nativePauseHintSession(halPtr); @@ -612,8 +627,12 @@ public final class HintManagerService extends SystemService { @VisibleForTesting final class BinderService extends IHintManager.Stub { @Override - public IHintSession createHintSession(IBinder token, int[] tids, long durationNanos) { - if (!isHalSupported()) return null; + public IHintSession createHintSessionWithConfig(@NonNull IBinder token, + @NonNull int[] tids, long durationNanos, @SessionTag int tag, + @Nullable SessionConfig config) { + if (!isHalSupported()) { + throw new UnsupportedOperationException("PowerHAL is not supported!"); + } java.util.Objects.requireNonNull(token); java.util.Objects.requireNonNull(tids); @@ -634,8 +653,35 @@ public final class HintManagerService extends SystemService { throw new SecurityException(errMsg); } - long halSessionPtr = mNativeWrapper.halCreateHintSession(callingTgid, callingUid, - tids, durationNanos); + Long halSessionPtr = null; + if (mConfigCreationSupport.get()) { + try { + halSessionPtr = mNativeWrapper.halCreateHintSessionWithConfig( + callingTgid, callingUid, tids, durationNanos, tag, config); + } catch (UnsupportedOperationException e) { + mConfigCreationSupport.set(false); + } catch (IllegalStateException e) { + Slog.e("createHintSessionWithConfig failed: ", e.getMessage()); + throw new IllegalStateException( + "createHintSessionWithConfig failed: " + e.getMessage()); + } + } + + if (halSessionPtr == null) { + try { + halSessionPtr = mNativeWrapper.halCreateHintSession(callingTgid, + callingUid, tids, durationNanos); + } catch (UnsupportedOperationException e) { + Slog.w("createHintSession unsupported: ", e.getMessage()); + throw new UnsupportedOperationException( + "createHintSession unsupported: " + e.getMessage()); + } catch (IllegalStateException e) { + Slog.e("createHintSession failed: ", e.getMessage()); + throw new IllegalStateException( + "createHintSession failed: " + e.getMessage()); + } + } + if (powerhintThreadCleanup()) { synchronized (mNonIsolatedTidsLock) { for (int i = nonIsolated.size() - 1; i >= 0; i--) { @@ -644,9 +690,6 @@ public final class HintManagerService extends SystemService { } } } - if (halSessionPtr == 0) { - return null; - } AppHintSession hs = new AppHintSession(callingUid, callingTgid, tids, token, halSessionPtr, durationNanos); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 54e932a80ee9..8c998c3bfdb9 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -8363,7 +8363,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * aspect ratio. */ boolean shouldCreateCompatDisplayInsets() { - if (mLetterboxUiController.shouldApplyUserFullscreenOverride()) { + if (mLetterboxUiController.hasFullscreenOverride()) { // If the user has forced the applications aspect ratio to be fullscreen, don't use size // compatibility mode in any situation. The user has been warned and therefore accepts // the risk of the application misbehaving. @@ -8378,7 +8378,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A default: // Fall through } - if (inMultiWindowMode() || getWindowConfiguration().hasWindowDecorCaption()) { + // Use root activity's info for tasks in multi-window mode, or fullscreen tasks in freeform + // task display areas, to ensure visual consistency across activity launches and exits in + // the same task. + final TaskDisplayArea tda = getTaskDisplayArea(); + if (inMultiWindowMode() || (tda != null && tda.inFreeformWindowingMode())) { final ActivityRecord root = task != null ? task.getRootActivity() : null; if (root != null && root != this && !root.shouldCreateCompatDisplayInsets()) { // If the root activity doesn't use size compatibility mode, the activities above diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index be7c18c49373..31754bf67cc8 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -467,13 +467,6 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { onRequestedOverrideConfigurationChanged(mRequestsTmpConfig); } - /** Sets the windowing mode for the configuration container. */ - void setDisplayWindowingMode(int windowingMode) { - mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration()); - mRequestsTmpConfig.windowConfiguration.setDisplayWindowingMode(windowingMode); - onRequestedOverrideConfigurationChanged(mRequestsTmpConfig); - } - /** * Returns true if this container is currently in multi-window mode. I.e. sharing the screen * with another activity. diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 00d42e0eb109..6c757a31f323 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -2456,7 +2456,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp config.windowConfiguration.setBounds(mTmpRect); config.windowConfiguration.setMaxBounds(mTmpRect); config.windowConfiguration.setWindowingMode(getWindowingMode()); - config.windowConfiguration.setDisplayWindowingMode(getWindowingMode()); computeScreenAppConfiguration(config, dw, dh, displayInfo.rotation); @@ -2834,11 +2833,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } } - @Override - void setDisplayWindowingMode(int windowingMode) { - setWindowingMode(windowingMode); - } - /** * See {@code WindowState#applyImeWindowsIfNeeded} for the details that we won't traverse the * IME window in some cases. @@ -2956,7 +2950,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp void onDisplayChanged(DisplayContent dc) { super.onDisplayChanged(dc); updateSystemGestureExclusionLimit(); - updateKeepClearAreas(); } void updateSystemGestureExclusionLimit() { @@ -4035,7 +4028,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } adjustForImeIfNeeded(); - updateKeepClearAreas(); // We may need to schedule some toast windows to be removed. The toasts for an app that // does not have input focus are removed within a timeout to prevent apps to redress @@ -6156,6 +6148,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp Region touchableRegion = Region.obtain(); w.getEffectiveTouchableRegion(touchableRegion); RegionUtils.forEachRect(touchableRegion, rect -> outUnrestricted.add(rect)); + touchableRegion.recycle(); } } diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 3a04bcd1df7d..dfee16440518 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -224,7 +224,6 @@ class InsetsStateController { if (changed) { notifyInsetsChanged(); mDisplayContent.updateSystemGestureExclusion(); - mDisplayContent.updateKeepClearAreas(); mDisplayContent.getDisplayPolicy().updateSystemBarAttributes(); } } diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 90a3b25303f5..2c27b98250c9 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -1880,7 +1880,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { mTempConfiguration.setTo(getRequestedOverrideConfiguration()); WindowConfiguration tempRequestWindowConfiguration = mTempConfiguration.windowConfiguration; tempRequestWindowConfiguration.setWindowingMode(windowingMode); - tempRequestWindowConfiguration.setDisplayWindowingMode(windowingMode); onRequestedOverrideConfigurationChanged(mTempConfiguration); } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index fc85af5c2303..7a8bea0c726b 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -1971,6 +1971,15 @@ class TaskFragment extends WindowContainer<WindowContainer> { return SCREEN_ORIENTATION_UNSET; } + @ActivityInfo.ScreenOrientation + @Override + protected int getOverrideOrientation() { + if (isEmbedded() && !isVisibleRequested()) { + return SCREEN_ORIENTATION_UNSPECIFIED; + } + return super.getOverrideOrientation(); + } + /** * Whether or not to allow this container to specify an app requested orientation. * diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 41804751ed4b..7afd34c90b0d 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -1025,7 +1025,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub break; } final Task task = wc.asTask(); - + if (task.isVisibleRequested() || task.isVisible()) { + effects |= TRANSACT_EFFECTS_LIFECYCLE; + } if (task.isLeafTask()) { mService.mTaskSupervisor .removeTask(task, true, REMOVE_FROM_RECENTS, "remove-task" diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp index 5b8ef19ea179..be188358b90b 100644 --- a/services/core/jni/com_android_server_hint_HintManagerService.cpp +++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp @@ -34,13 +34,13 @@ #include "jni.h" +using aidl::android::hardware::power::SessionConfig; using aidl::android::hardware::power::SessionHint; using aidl::android::hardware::power::SessionMode; +using aidl::android::hardware::power::SessionTag; using aidl::android::hardware::power::WorkDuration; using android::power::PowerHintSessionWrapper; -using android::base::StringPrintf; - namespace android { static struct { @@ -66,6 +66,15 @@ static int64_t getHintSessionPreferredRate() { return rate; } +void throwUnsupported(JNIEnv* env, const char* msg) { + env->ThrowNew(env->FindClass("java/lang/UnsupportedOperationException"), msg); +} + +void throwFailed(JNIEnv* env, const char* msg) { + // We throw IllegalStateException for all errors other than the "unsupported" ones + env->ThrowNew(env->FindClass("java/lang/IllegalStateException"), msg); +} + static jlong createHintSession(JNIEnv* env, int32_t tgid, int32_t uid, std::vector<int32_t>& threadIds, int64_t durationNanos) { auto result = gPowerHalController.createHintSession(tgid, uid, threadIds, durationNanos); @@ -76,10 +85,38 @@ static jlong createHintSession(JNIEnv* env, int32_t tgid, int32_t uid, return res.second ? session_ptr : 0; } else if (result.isFailed()) { ALOGW("createHintSession failed with message: %s", result.errorMessage()); + throwFailed(env, result.errorMessage()); + } else if (result.isUnsupported()) { + throwUnsupported(env, result.errorMessage()); + return -1; } return 0; } +static jlong createHintSessionWithConfig(JNIEnv* env, int32_t tgid, int32_t uid, + std::vector<int32_t> threadIds, int64_t durationNanos, + int32_t sessionTag, SessionConfig& config) { + auto result = + gPowerHalController.createHintSessionWithConfig(tgid, uid, threadIds, durationNanos, + static_cast<SessionTag>(sessionTag), + &config); + if (result.isOk()) { + jlong session_ptr = reinterpret_cast<jlong>(result.value().get()); + std::scoped_lock sessionLock(gSessionMapLock); + auto res = gSessionMap.insert({session_ptr, result.value()}); + if (!res.second) { + throwFailed(env, "PowerHAL provided an invalid session"); + return 0; + } + return session_ptr; + } else if (result.isUnsupported()) { + throwUnsupported(env, result.errorMessage()); + return -1; + } + throwFailed(env, result.errorMessage()); + return 0; +} + static void pauseHintSession(JNIEnv* env, int64_t session_ptr) { auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr); appSession->pause(); @@ -136,13 +173,34 @@ static jlong nativeCreateHintSession(JNIEnv* env, jclass /* clazz */, jint tgid, jintArray tids, jlong durationNanos) { ScopedIntArrayRO tidArray(env, tids); if (nullptr == tidArray.get() || tidArray.size() == 0) { - ALOGW("GetIntArrayElements returns nullptr."); + ALOGW("nativeCreateHintSession: GetIntArrayElements returns nullptr."); return 0; } std::vector<int32_t> threadIds(tidArray.get(), tidArray.get() + tidArray.size()); return createHintSession(env, tgid, uid, threadIds, durationNanos); } +static jlong nativeCreateHintSessionWithConfig(JNIEnv* env, jclass /* clazz */, jint tgid, jint uid, + jintArray tids, jlong durationNanos, jint sessionTag, + jobject sessionConfig) { + ScopedIntArrayRO tidArray(env, tids); + if (nullptr == tidArray.get() || tidArray.size() == 0) { + ALOGW("nativeCreateHintSessionWithConfig: GetIntArrayElements returns nullptr."); + return 0; + } + std::vector<int32_t> threadIds(tidArray.get(), tidArray.get() + tidArray.size()); + SessionConfig config; + jlong out = createHintSessionWithConfig(env, tgid, uid, std::move(threadIds), durationNanos, + sessionTag, config); + if (out <= 0) { + return out; + } + static jclass configClass = env->FindClass("android/hardware/power/SessionConfig"); + static jfieldID fid = env->GetFieldID(configClass, "id", "J"); + env->SetLongField(sessionConfig, fid, config.id); + return out; +} + static void nativePauseHintSession(JNIEnv* env, jclass /* clazz */, jlong session_ptr) { pauseHintSession(env, session_ptr); } @@ -215,6 +273,8 @@ static const JNINativeMethod sHintManagerServiceMethods[] = { {"nativeInit", "()V", (void*)nativeInit}, {"nativeGetHintSessionPreferredRate", "()J", (void*)nativeGetHintSessionPreferredRate}, {"nativeCreateHintSession", "(II[IJ)J", (void*)nativeCreateHintSession}, + {"nativeCreateHintSessionWithConfig", "(II[IJILandroid/hardware/power/SessionConfig;)J", + (void*)nativeCreateHintSessionWithConfig}, {"nativePauseHintSession", "(J)V", (void*)nativePauseHintSession}, {"nativeResumeHintSession", "(J)V", (void*)nativeResumeHintSession}, {"nativeCloseHintSession", "(J)V", (void*)nativeCloseHintSession}, diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java index 95c4407e3963..cc5573bb01d8 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java @@ -103,6 +103,12 @@ public class BookStyleDeviceStatePolicy extends DeviceStatePolicy implements final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); mEnablePostureBasedClosedState = featureFlags.enableFoldablesPostureBasedClosedState(); + if (mEnablePostureBasedClosedState) { + // This configuration doesn't require listening to hall sensor, it solely relies + // on the fused hinge angle sensor + hallSensor = null; + } + mIsDualDisplayBlockingEnabled = featureFlags.enableDualDisplayBlocking(); final DeviceStatePredicateWrapper[] configuration = createConfiguration( diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java index bc8643f3d173..daeaa9833d78 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java @@ -95,6 +95,7 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, private final Sensor mHingeAngleSensor; private final DisplayManager mDisplayManager; + @Nullable private final Sensor mHallSensor; private static final Predicate<FoldableDeviceStateProvider> ALLOWED = p -> true; @@ -122,7 +123,7 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, @NonNull Context context, @NonNull SensorManager sensorManager, @NonNull Sensor hingeAngleSensor, - @NonNull Sensor hallSensor, + @Nullable Sensor hallSensor, @NonNull DisplayManager displayManager, @NonNull DeviceStatePredicateWrapper[] deviceStatePredicateWrappers) { this(new FeatureFlagsImpl(), context, sensorManager, hingeAngleSensor, hallSensor, @@ -135,7 +136,7 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, @NonNull Context context, @NonNull SensorManager sensorManager, @NonNull Sensor hingeAngleSensor, - @NonNull Sensor hallSensor, + @Nullable Sensor hallSensor, @NonNull DisplayManager displayManager, @NonNull DeviceStatePredicateWrapper[] deviceStatePredicateWrappers) { @@ -149,7 +150,9 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, mIsDualDisplayBlockingEnabled = featureFlags.enableDualDisplayBlocking(); sensorManager.registerListener(this, mHingeAngleSensor, SENSOR_DELAY_FASTEST); - sensorManager.registerListener(this, mHallSensor, SENSOR_DELAY_FASTEST); + if (hallSensor != null) { + sensorManager.registerListener(this, mHallSensor, SENSOR_DELAY_FASTEST); + } mOrderedStates = new DeviceState[deviceStatePredicateWrappers.length]; for (int i = 0; i < deviceStatePredicateWrappers.length; i++) { @@ -324,7 +327,7 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, @Override public void onSensorChanged(SensorEvent event) { synchronized (mLock) { - if (event.sensor == mHallSensor) { + if (mHallSensor != null && event.sensor == mHallSensor) { mLastHallSensorEvent = event; } else if (event.sensor == mHingeAngleSensor) { mLastHingeAngleSensorEvent = event; @@ -359,11 +362,11 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, } @GuardedBy("mLock") - private void dumpSensorValues(Sensor sensor, @Nullable SensorEvent event) { + private void dumpSensorValues(@Nullable Sensor sensor, @Nullable SensorEvent event) { Slog.i(TAG, toSensorValueString(sensor, event)); } - private String toSensorValueString(Sensor sensor, @Nullable SensorEvent event) { + private String toSensorValueString(@Nullable Sensor sensor, @Nullable SensorEvent event) { String sensorString = sensor == null ? "null" : sensor.getName(); String eventValues = event == null ? "null" : Arrays.toString(event.values); return sensorString + " : " + eventValues; diff --git a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java index 901f24dd9b0b..9f07aa8c1a41 100644 --- a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java +++ b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java @@ -219,6 +219,26 @@ public final class BookStyleDeviceStatePolicyTest { } @Test + public void test_postureBasedClosedState_createPolicy_doesNotRegisterHallSensor() { + mFakeFeatureFlags.setFlag(Flags.FLAG_ENABLE_FOLDABLES_POSTURE_BASED_CLOSED_STATE, true); + clearInvocations(mSensorManager); + + mInstrumentation.runOnMainSync(() -> mProvider = createProvider()); + + verify(mSensorManager, never()).registerListener(any(), eq(mHallSensor), anyInt()); + } + + @Test + public void test_postureBasedClosedStateDisabled_createPolicy_registersHallSensor() { + mFakeFeatureFlags.setFlag(Flags.FLAG_ENABLE_FOLDABLES_POSTURE_BASED_CLOSED_STATE, false); + clearInvocations(mSensorManager); + + mInstrumentation.runOnMainSync(() -> mProvider = createProvider()); + + verify(mSensorManager).registerListener(any(), eq(mHallSensor), anyInt()); + } + + @Test public void test_noSensorEventsYet_reportOpenedState() { mProvider.setListener(mListener); verify(mListener).onStateChanged(mDeviceStateCaptor.capture()); diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java index 510e7c42f12d..5902caae443d 100644 --- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java @@ -22,6 +22,7 @@ import static com.android.server.power.hint.HintManagerService.CLEAN_UP_UID_DELA import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; @@ -41,6 +42,8 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.content.Context; +import android.hardware.power.SessionConfig; +import android.hardware.power.SessionTag; import android.hardware.power.WorkDuration; import android.os.Binder; import android.os.IBinder; @@ -63,6 +66,8 @@ import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.Arrays; @@ -97,6 +102,7 @@ public class HintManagerServiceTest { private static final long DEFAULT_HINT_PREFERRED_RATE = 16666666L; private static final long DEFAULT_TARGET_DURATION = 16666666L; + private static final long DOUBLED_TARGET_DURATION = 33333333L; private static final long CONCURRENCY_TEST_DURATION_SEC = 10; private static final int UID = Process.myUid(); private static final int TID = Process.myPid(); @@ -106,6 +112,8 @@ public class HintManagerServiceTest { private static final int[] SESSION_TIDS_C = new int[] {TID}; private static final long[] DURATIONS_THREE = new long[] {1L, 100L, 1000L}; private static final long[] TIMESTAMPS_THREE = new long[] {1L, 2L, 3L}; + private static final long[] SESSION_PTRS = new long[] {11L, 22L, 33L}; + private static final long[] SESSION_IDS = new long[] {1L, 11L, 111L}; private static final long[] DURATIONS_ZERO = new long[] {}; private static final long[] TIMESTAMPS_ZERO = new long[] {}; private static final long[] TIMESTAMPS_TWO = new long[] {1L, 2L}; @@ -129,21 +137,61 @@ public class HintManagerServiceTest { private HintManagerService mService; + private static Answer<Long> fakeCreateWithConfig(Long ptr, Long sessionId) { + return new Answer<Long>() { + public Long answer(InvocationOnMock invocation) { + ((SessionConfig) invocation.getArguments()[5]).id = sessionId; + return ptr; + } + }; + } + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); when(mNativeWrapperMock.halGetHintSessionPreferredRate()) .thenReturn(DEFAULT_HINT_PREFERRED_RATE); when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_A), - eq(DEFAULT_TARGET_DURATION))).thenReturn(1L); + eq(DEFAULT_TARGET_DURATION))).thenReturn(SESSION_PTRS[0]); when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_B), - eq(DEFAULT_TARGET_DURATION))).thenReturn(2L); + eq(DOUBLED_TARGET_DURATION))).thenReturn(SESSION_PTRS[1]); when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_C), - eq(0L))).thenReturn(1L); + eq(0L))).thenReturn(SESSION_PTRS[2]); + when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID), + eq(SESSION_TIDS_A), eq(DEFAULT_TARGET_DURATION), anyInt(), + any(SessionConfig.class))).thenAnswer(fakeCreateWithConfig(SESSION_PTRS[0], + SESSION_IDS[0])); + when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID), + eq(SESSION_TIDS_B), eq(DOUBLED_TARGET_DURATION), anyInt(), + any(SessionConfig.class))).thenAnswer(fakeCreateWithConfig(SESSION_PTRS[1], + SESSION_IDS[1])); + when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID), + eq(SESSION_TIDS_C), eq(0L), anyInt(), + any(SessionConfig.class))).thenAnswer(fakeCreateWithConfig(SESSION_PTRS[2], + SESSION_IDS[2])); + LocalServices.removeServiceForTest(ActivityManagerInternal.class); LocalServices.addService(ActivityManagerInternal.class, mAmInternalMock); } + /** + * Mocks the creation calls, but without support for new createHintSessionWithConfig method + */ + public void makeConfigCreationUnsupported() { + reset(mNativeWrapperMock); + when(mNativeWrapperMock.halGetHintSessionPreferredRate()) + .thenReturn(DEFAULT_HINT_PREFERRED_RATE); + when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_A), + eq(DEFAULT_TARGET_DURATION))).thenReturn(SESSION_PTRS[0]); + when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_B), + eq(DOUBLED_TARGET_DURATION))).thenReturn(SESSION_PTRS[1]); + when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_C), + eq(0L))).thenReturn(SESSION_PTRS[2]); + when(mNativeWrapperMock.halCreateHintSessionWithConfig(anyInt(), anyInt(), + any(int[].class), anyLong(), anyInt(), + any(SessionConfig.class))).thenThrow(new UnsupportedOperationException()); + } + static class NativeWrapperFake extends NativeWrapper { @Override public void halInit() { @@ -160,6 +208,12 @@ public class HintManagerServiceTest { } @Override + public long halCreateHintSessionWithConfig(int tgid, int uid, int[] tids, + long durationNanos, int tag, SessionConfig config) { + return 1; + } + + @Override public void halPauseHintSession(long halPtr) { } @@ -224,27 +278,57 @@ public class HintManagerServiceTest { IBinder token = new Binder(); // Make sure we throw exception when adding a TID doesn't belong to the processes // In this case, we add `init` PID into the list. + SessionConfig config = new SessionConfig(); assertThrows(SecurityException.class, - () -> service.getBinderServiceInstance().createHintSession(token, - new int[]{TID, 1}, DEFAULT_TARGET_DURATION)); + () -> service.getBinderServiceInstance().createHintSessionWithConfig(token, + new int[]{TID, 1}, DEFAULT_TARGET_DURATION, SessionTag.OTHER, config)); + } + + @Test + public void testCreateHintSessionFallback() throws Exception { + HintManagerService service = createService(); + IBinder token = new Binder(); + makeConfigCreationUnsupported(); + + IHintSession a = service.getBinderServiceInstance().createHintSessionWithConfig(token, + SESSION_TIDS_A, DEFAULT_TARGET_DURATION, SessionTag.OTHER, new SessionConfig()); + assertNotNull(a); + + IHintSession b = service.getBinderServiceInstance().createHintSessionWithConfig(token, + SESSION_TIDS_B, DOUBLED_TARGET_DURATION, SessionTag.OTHER, new SessionConfig()); + assertNotEquals(a, b); + + IHintSession c = service.getBinderServiceInstance().createHintSessionWithConfig(token, + SESSION_TIDS_C, 0L, SessionTag.OTHER, new SessionConfig()); + assertNotNull(c); + verify(mNativeWrapperMock, times(3)).halCreateHintSession(anyInt(), anyInt(), + any(int[].class), anyLong()); } @Test - public void testCreateHintSession() throws Exception { + public void testCreateHintSessionWithConfig() throws Exception { HintManagerService service = createService(); IBinder token = new Binder(); - IHintSession a = service.getBinderServiceInstance().createHintSession(token, - SESSION_TIDS_A, DEFAULT_TARGET_DURATION); + SessionConfig config = new SessionConfig(); + IHintSession a = service.getBinderServiceInstance().createHintSessionWithConfig(token, + SESSION_TIDS_A, DEFAULT_TARGET_DURATION, SessionTag.OTHER, config); assertNotNull(a); + assertEquals(SESSION_IDS[0], config.id); - IHintSession b = service.getBinderServiceInstance().createHintSession(token, - SESSION_TIDS_B, DEFAULT_TARGET_DURATION); + SessionConfig config2 = new SessionConfig(); + IHintSession b = service.getBinderServiceInstance().createHintSessionWithConfig(token, + SESSION_TIDS_B, DOUBLED_TARGET_DURATION, SessionTag.APP, config2); assertNotEquals(a, b); + assertEquals(SESSION_IDS[1], config2.id); - IHintSession c = service.getBinderServiceInstance().createHintSession(token, - SESSION_TIDS_C, 0L); + SessionConfig config3 = new SessionConfig(); + IHintSession c = service.getBinderServiceInstance().createHintSessionWithConfig(token, + SESSION_TIDS_C, 0L, SessionTag.GAME, config3); assertNotNull(c); + assertEquals(SESSION_IDS[2], config3.id); + verify(mNativeWrapperMock, times(3)).halCreateHintSessionWithConfig(anyInt(), anyInt(), + any(int[].class), anyLong(), anyInt(), any(SessionConfig.class)); } @Test @@ -253,7 +337,8 @@ public class HintManagerServiceTest { IBinder token = new Binder(); AppHintSession a = (AppHintSession) service.getBinderServiceInstance() - .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION); + .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION, + SessionTag.OTHER, new SessionConfig()); // Set session to background and calling updateHintAllowed() would invoke pause(); service.mUidObserver.onUidStateChanged( @@ -288,8 +373,8 @@ public class HintManagerServiceTest { HintManagerService service = createService(); IBinder token = new Binder(); - IHintSession a = service.getBinderServiceInstance().createHintSession(token, - SESSION_TIDS_A, DEFAULT_TARGET_DURATION); + IHintSession a = service.getBinderServiceInstance().createHintSessionWithConfig(token, + SESSION_TIDS_A, DEFAULT_TARGET_DURATION, SessionTag.OTHER, new SessionConfig()); a.close(); verify(mNativeWrapperMock, times(1)).halCloseHintSession(anyLong()); @@ -300,8 +385,8 @@ public class HintManagerServiceTest { HintManagerService service = createService(); IBinder token = new Binder(); - IHintSession a = service.getBinderServiceInstance().createHintSession(token, - SESSION_TIDS_A, DEFAULT_TARGET_DURATION); + IHintSession a = service.getBinderServiceInstance().createHintSessionWithConfig(token, + SESSION_TIDS_A, DEFAULT_TARGET_DURATION, SessionTag.OTHER, new SessionConfig()); assertThrows(IllegalArgumentException.class, () -> { a.updateTargetWorkDuration(-1L); @@ -321,7 +406,8 @@ public class HintManagerServiceTest { IBinder token = new Binder(); AppHintSession a = (AppHintSession) service.getBinderServiceInstance() - .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION); + .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION, + SessionTag.OTHER, new SessionConfig()); a.updateTargetWorkDuration(100L); a.reportActualWorkDuration(DURATIONS_THREE, TIMESTAMPS_THREE); @@ -363,7 +449,8 @@ public class HintManagerServiceTest { IBinder token = new Binder(); AppHintSession a = (AppHintSession) service.getBinderServiceInstance() - .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION); + .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION, + SessionTag.OTHER, new SessionConfig()); a.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET); verify(mNativeWrapperMock, times(1)).halSendHint(anyLong(), @@ -389,7 +476,8 @@ public class HintManagerServiceTest { IBinder token = new Binder(); AppHintSession a = (AppHintSession) service.getBinderServiceInstance() - .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION); + .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION, + SessionTag.OTHER, new SessionConfig()); service.mUidObserver.onUidStateChanged( a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0); @@ -410,7 +498,8 @@ public class HintManagerServiceTest { IBinder token = new Binder(); AppHintSession a = (AppHintSession) service.getBinderServiceInstance() - .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION); + .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION, + SessionTag.OTHER, new SessionConfig()); service.mUidObserver.onUidStateChanged( a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0); @@ -423,7 +512,8 @@ public class HintManagerServiceTest { IBinder token = new Binder(); AppHintSession a = (AppHintSession) service.getBinderServiceInstance() - .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION); + .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION, + SessionTag.OTHER, new SessionConfig()); a.updateTargetWorkDuration(100L); @@ -454,10 +544,12 @@ public class HintManagerServiceTest { int threadCount = 3; int[] tids1 = createThreads(threadCount, stopLatch1); long sessionPtr1 = 111; - when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(tids1), - eq(DEFAULT_TARGET_DURATION))).thenReturn(sessionPtr1); + when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID), eq(tids1), + eq(DEFAULT_TARGET_DURATION), anyInt(), any(SessionConfig.class))) + .thenReturn(sessionPtr1); AppHintSession session1 = (AppHintSession) service.getBinderServiceInstance() - .createHintSession(token, tids1, DEFAULT_TARGET_DURATION); + .createHintSessionWithConfig(token, tids1, DEFAULT_TARGET_DURATION, + SessionTag.OTHER, new SessionConfig()); assertNotNull(session1); // for test only to avoid conflicting with any real thread that exists on device @@ -473,10 +565,12 @@ public class HintManagerServiceTest { tids2WithIsolated[threadCount] = isoProc1; tids2WithIsolated[threadCount + 1] = isoProc2; long sessionPtr2 = 222; - when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(tids2WithIsolated), - eq(DEFAULT_TARGET_DURATION))).thenReturn(sessionPtr2); + when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID), + eq(tids2WithIsolated), eq(DEFAULT_TARGET_DURATION), anyInt(), + any(SessionConfig.class))).thenReturn(sessionPtr2); AppHintSession session2 = (AppHintSession) service.getBinderServiceInstance() - .createHintSession(token, tids2WithIsolated, DEFAULT_TARGET_DURATION); + .createHintSessionWithConfig(token, tids2WithIsolated, + DEFAULT_TARGET_DURATION, SessionTag.OTHER, new SessionConfig()); assertNotNull(session2); // trigger clean up through UID state change by making the process background @@ -608,7 +702,8 @@ public class HintManagerServiceTest { IBinder token = new Binder(); AppHintSession a = (AppHintSession) service.getBinderServiceInstance() - .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION); + .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION, + SessionTag.OTHER, new SessionConfig()); a.setMode(0, true); verify(mNativeWrapperMock, times(1)).halSetMode(anyLong(), @@ -726,7 +821,8 @@ public class HintManagerServiceTest { AtomicReference<Boolean> shouldRun) throws Exception { IBinder token = new Binder(); AppHintSession a = (AppHintSession) service.getBinderServiceInstance() - .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION); + .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION, + SessionTag.OTHER, new SessionConfig()); // we will start some threads and get their valid TIDs to update int threadCount = 3; // the list of TIDs @@ -793,7 +889,8 @@ public class HintManagerServiceTest { IBinder token = new Binder(); AppHintSession a = (AppHintSession) service.getBinderServiceInstance() - .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION); + .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION, + SessionTag.OTHER, new SessionConfig()); a.updateTargetWorkDuration(100L); a.reportActualWorkDuration2(WORK_DURATIONS_FIVE); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java index ae3683961d61..983e694a8f1a 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java @@ -70,6 +70,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.platform.test.flag.junit.SetFlagsRule; import android.service.notification.INotificationListener; +import android.service.notification.IStatusBarNotificationHolder; import android.service.notification.NotificationListenerFilter; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationRankingUpdate; @@ -90,6 +91,7 @@ import com.google.common.collect.ImmutableList; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -154,6 +156,11 @@ public class NotificationListenersTest extends UiServiceTestCase { .thenReturn(new ArrayList<>()); mNm.mHandler = mock(NotificationManagerService.WorkerHandler.class); mNm.mAssistants = mock(NotificationManagerService.NotificationAssistants.class); + FieldSetter.setField(mNm, + NotificationManagerService.class.getDeclaredField("mListeners"), + mListeners); + doReturn(android.service.notification.NotificationListenerService.TRIM_FULL) + .when(mListeners).getOnNotificationPostedTrim(any()); } @Test @@ -827,6 +834,68 @@ public class NotificationListenersTest extends UiServiceTestCase { verify(mListeners, never()).redactStatusBarNotification(eq(sbn)); } + @Test + public void testListenerPost_UpdateLifetimeExtended() throws Exception { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR); + + // Create original notification, with FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY. + String pkg = "pkg"; + int uid = 9; + UserHandle userHandle = UserHandle.getUserHandleForUid(uid); + NotificationChannel channel = new NotificationChannel("id", "name", + NotificationManager.IMPORTANCE_HIGH); + Notification.Builder nb = new Notification.Builder(mContext, channel.getId()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setFlag(Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true); + StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0, + nb.build(), userHandle, null, 0); + NotificationRecord old = new NotificationRecord(mContext, sbn, channel); + + // Creates updated notification (without FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) + Notification.Builder nb2 = new Notification.Builder(mContext, channel.getId()) + .setContentTitle("new title") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setFlag(Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, false); + StatusBarNotification sbn2 = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0, + nb2.build(), userHandle, null, 0); + NotificationRecord toPost = new NotificationRecord(mContext, sbn2, channel); + + // Create system ui-like service. + ManagedServices.ManagedServiceInfo info = mListeners.new ManagedServiceInfo( + null, new ComponentName("a", "a"), sbn2.getUserId(), false, null, 33, 33); + info.isSystemUi = true; + INotificationListener l1 = mock(INotificationListener.class); + info.service = l1; + List<ManagedServices.ManagedServiceInfo> services = ImmutableList.of(info); + when(mListeners.getServices()).thenReturn(services); + + FieldSetter.setField(mNm, + NotificationManagerService.class.getDeclaredField("mHandler"), + mock(NotificationManagerService.WorkerHandler.class)); + doReturn(true).when(mNm).isVisibleToListener(any(), anyInt(), any()); + doReturn(mock(NotificationRankingUpdate.class)).when(mNm).makeRankingUpdateLocked(info); + doReturn(false).when(mNm).isInLockDownMode(anyInt()); + doNothing().when(mNm).updateUriPermissions(any(), any(), any(), anyInt()); + doReturn(sbn2).when(mListeners).redactStatusBarNotification(sbn2); + doReturn(sbn2).when(mListeners).redactStatusBarNotification(any()); + + // The notification change is posted to the service listener. + mListeners.notifyPostedLocked(toPost, old); + + // Verify that the post occcurs with the updated notification value. + ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(mNm.mHandler, times(1)).post(runnableCaptor.capture()); + runnableCaptor.getValue().run(); + ArgumentCaptor<IStatusBarNotificationHolder> sbnCaptor = + ArgumentCaptor.forClass(IStatusBarNotificationHolder.class); + verify(l1, times(1)).onNotificationPosted(sbnCaptor.capture(), any()); + StatusBarNotification sbnResult = sbnCaptor.getValue().get(); + assertThat(sbnResult.getNotification() + .extras.getCharSequence(Notification.EXTRA_TITLE).toString()) + .isEqualTo("new title"); + } + /** * Helper method to test the thread safety of some operations. * diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 011f2e39d6f8..aec47146e833 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -106,6 +106,7 @@ import static android.service.notification.NotificationListenerService.Ranking.U import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; + import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER; @@ -118,11 +119,11 @@ import static com.android.server.notification.NotificationManagerService.TAG; import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED; import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED; import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED; + import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; @@ -131,6 +132,7 @@ import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertSame; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; + import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.isNull; @@ -157,6 +159,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; + import android.Manifest; import android.annotation.Nullable; import android.annotation.SuppressLint; @@ -270,8 +275,10 @@ import android.util.Pair; import android.util.Xml; import android.view.accessibility.AccessibilityManager; import android.widget.RemoteViews; + import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; + import com.android.internal.R; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.config.sysui.TestableFlagResolver; @@ -303,10 +310,12 @@ import com.android.server.uri.UriGrantsManagerInternal; import com.android.server.utils.quota.MultiRateLimiter; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; + import com.google.android.collect.Lists; import com.google.common.collect.ImmutableList; import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; + import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -5934,6 +5943,45 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) + public void testUpdate_DirectReplyLifetimeExtendedUpdateSucceeds() throws Exception { + // Creates a lifetime extended notification. + NotificationRecord original = generateNotificationRecord(mTestNotificationChannel); + original.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; + mService.addNotification(original); + + // Post an update for that notification. + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, original.getSbn().getId(), + original.getSbn().getTag(), mUid, 0, + new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setContentTitle("new title").build(), + UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord update = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + mService.addEnqueuedNotification(update); + + NotificationManagerService.PostNotificationRunnable runnable = + mService.new PostNotificationRunnable(update.getKey(), + update.getSbn().getPackageName(), + update.getUid(), + mPostNotificationTrackerFactory.newTracker(null)); + runnable.run(); + waitForIdle(); + + // Checks the update was sent, and that update contains the new title, and does not contain + // the lifetime extension flag. + ArgumentCaptor<NotificationRecord> captor = + ArgumentCaptor.forClass(NotificationRecord.class); + verify(mListeners, times(1)).prepareNotifyPostedLocked(captor.capture(), any(), + anyBoolean()); + assertThat(captor.getValue().getNotification().flags + & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(0); + assertThat(captor.getValue() + .getNotification().extras.getCharSequence(Notification.EXTRA_TITLE).toString()) + .isEqualTo("new title"); + } + + @Test public void testStats_updatedOnUserExpansion() throws Exception { NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); mService.addNotification(r); @@ -12602,7 +12650,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // the notifyPostedLocked function is called twice. verify(mWorkerHandler, times(2)).postDelayed(any(Runnable.class), anyLong()); - //verify(mListeners, times(2)).notifyPostedLocked(any(), any()); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java index f724510eeb73..8add2f957a07 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java @@ -21,8 +21,8 @@ import static com.google.common.truth.Truth.assertThat; import android.app.AutomaticZenRule; import android.provider.Settings; import android.service.notification.ZenPolicy; -import android.test.suitebuilder.annotation.SmallTest; +import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.os.dnd.ActiveRuleType; diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 9697c65dc1ea..000162a9e705 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -502,7 +502,6 @@ public class SizeCompatTests extends WindowTestsBase { final WindowConfiguration translucentWinConf = requestedConfig.windowConfiguration; translucentWinConf.setActivityType(ACTIVITY_TYPE_STANDARD); translucentWinConf.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); - translucentWinConf.setDisplayWindowingMode(WINDOWING_MODE_MULTI_WINDOW); translucentWinConf.setAlwaysOnTop(true); translucentActivity.onRequestedOverrideConfigurationChanged(requestedConfig); @@ -511,7 +510,6 @@ public class SizeCompatTests extends WindowTestsBase { // The original override of WindowConfiguration should keep. assertEquals(ACTIVITY_TYPE_STANDARD, translucentActivity.getActivityType()); assertEquals(WINDOWING_MODE_MULTI_WINDOW, translucentWinConf.getWindowingMode()); - assertEquals(WINDOWING_MODE_MULTI_WINDOW, translucentWinConf.getDisplayWindowingMode()); assertTrue(translucentWinConf.isAlwaysOnTop()); // Unless display is going to be rotated, it should always inherit from parent. assertEquals(ROTATION_UNDEFINED, translucentWinConf.getDisplayRotation()); @@ -1384,6 +1382,25 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + @EnableCompatChanges({ActivityInfo.OVERRIDE_ANY_ORIENTATION_TO_USER}) + public void testShouldNotCreateCompatDisplays_systemFullscreenOverride() { + setUpDisplaySizeWithApp(1000, 2500); + + // Make the task root resizable. + mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE; + + // Create an activity on the same task. + final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false, + RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + + // Simulate the user selecting the fullscreen user aspect ratio override + spyOn(activity.mLetterboxUiController); + doReturn(true).when(activity.mLetterboxUiController) + .isSystemOverrideToFullscreenEnabled(); + assertFalse(activity.shouldCreateCompatDisplayInsets()); + } + + @Test @EnableCompatChanges({ActivityInfo.NEVER_SANDBOX_DISPLAY_APIS}) public void testNeverSandboxDisplayApis_configEnabled_sandboxingNotApplied() { setUpDisplaySizeWithApp(1000, 1200); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 002a3d5a0d53..65a81c419d37 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -1050,6 +1050,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // TaskFragment override orientation should be set for a system organizer. final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(fragmentToken); assertNotNull(taskFragment); + + taskFragment.setVisibleRequested(true); assertEquals(SCREEN_ORIENTATION_BEHIND, taskFragment.getOverrideOrientation()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index 4837fcbfc262..a90a158e0c2a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -757,6 +757,7 @@ public class TaskFragmentTest extends WindowTestsBase { final Task task = createTask(mDisplayContent); final TaskFragment tf = createTaskFragmentWithActivity(task); final ActivityRecord activity = tf.getTopMostActivity(); + tf.setVisibleRequested(true); tf.setOverrideOrientation(SCREEN_ORIENTATION_BEHIND); // Should report the override orientation @@ -768,6 +769,26 @@ public class TaskFragmentTest extends WindowTestsBase { } @Test + public void testGetOrientation_reportOverrideOrientation_whenInvisible() { + final Task task = createTask(mDisplayContent); + final TaskFragment tf = createTaskFragmentWithActivity(task); + final ActivityRecord activity = tf.getTopMostActivity(); + tf.setVisibleRequested(false); + tf.setOverrideOrientation(SCREEN_ORIENTATION_BEHIND); + + // Should report SCREEN_ORIENTATION_UNSPECIFIED for the override orientation when invisible + assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, tf.getOverrideOrientation()); + + // Should report SCREEN_ORIENTATION_UNSET for the orientation + assertEquals(SCREEN_ORIENTATION_UNSET, tf.getOrientation(SCREEN_ORIENTATION_UNSET)); + + // Should report SCREEN_ORIENTATION_UNSET even if the activity requests a different + // value + activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); + assertEquals(SCREEN_ORIENTATION_UNSET, tf.getOrientation(SCREEN_ORIENTATION_UNSET)); + } + + @Test public void testUpdateImeParentForActivityEmbedding() { // Setup two activities in ActivityEmbedding. final Task task = createTask(mDisplayContent); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowConfigurationTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowConfigurationTests.java index 38aac7cfee22..eca51aed334e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowConfigurationTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowConfigurationTests.java @@ -16,9 +16,7 @@ package com.android.server.wm; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; @@ -208,43 +206,6 @@ public class WindowConfigurationTests extends WindowTestsBase { /** Ensure the window always has a caption in Freeform window mode or display mode. */ @Test - public void testCaptionShownForFreeformWindowingMode() { - final WindowConfiguration config = new WindowConfiguration(); - config.setActivityType(ACTIVITY_TYPE_STANDARD); - config.setWindowingMode(WINDOWING_MODE_FREEFORM); - config.setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN); - assertTrue(config.hasWindowDecorCaption()); - - config.setDisplayWindowingMode(WINDOWING_MODE_FREEFORM); - assertTrue(config.hasWindowDecorCaption()); - - config.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - assertTrue(config.hasWindowDecorCaption()); - - config.setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN); - assertFalse(config.hasWindowDecorCaption()); - } - - /** Caption should not show for non-standard activity window. */ - @Test - public void testCaptionNotShownForNonStandardActivityType() { - final WindowConfiguration config = new WindowConfiguration(); - config.setActivityType(ACTIVITY_TYPE_HOME); - config.setWindowingMode(WINDOWING_MODE_FREEFORM); - config.setDisplayWindowingMode(WINDOWING_MODE_FREEFORM); - assertFalse(config.hasWindowDecorCaption()); - - config.setActivityType(ACTIVITY_TYPE_ASSISTANT); - assertFalse(config.hasWindowDecorCaption()); - - config.setActivityType(ACTIVITY_TYPE_RECENTS); - assertFalse(config.hasWindowDecorCaption()); - - config.setActivityType(ACTIVITY_TYPE_STANDARD); - assertTrue(config.hasWindowDecorCaption()); - } - - @Test public void testMaskedSetTo() { final WindowConfiguration config = new WindowConfiguration(); final WindowConfiguration other = new WindowConfiguration(); diff --git a/tests/FsVerityTest/FsVerityTestApp/src/com/android/fsverity/Helper.java b/tests/FsVerityTest/FsVerityTestApp/src/com/android/fsverity/Helper.java index 2ed4fec4a93c..c52be7c2b0c6 100644 --- a/tests/FsVerityTest/FsVerityTestApp/src/com/android/fsverity/Helper.java +++ b/tests/FsVerityTest/FsVerityTestApp/src/com/android/fsverity/Helper.java @@ -27,6 +27,9 @@ import android.util.Log; import androidx.test.core.app.ApplicationProvider; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.compatibility.common.util.AdoptShellPermissionsRule; + +import org.junit.Rule; import org.junit.Test; import java.io.FileOutputStream; @@ -46,6 +49,12 @@ public class Helper { private static final long BLOCK_SIZE = 4096; + @Rule + public final AdoptShellPermissionsRule mAdoptShellPermissionsRule = + new AdoptShellPermissionsRule( + InstrumentationRegistry.getInstrumentation().getUiAutomation(), + android.Manifest.permission.SETUP_FSVERITY); + @Test public void prepareTest() throws Exception { Context context = ApplicationProvider.getApplicationContext(); |