diff options
178 files changed, 2733 insertions, 2612 deletions
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 2887d228e1b6..fa8fe3bf5458 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1755,6 +1755,12 @@ public class ActivityManager { private int mNavigationBarColor; @Appearance private int mSystemBarsAppearance; + /** + * Similar to {@link TaskDescription#mSystemBarsAppearance}, but is taken from the topmost + * fully opaque (i.e. non transparent) activity in the task. + */ + @Appearance + private int mTopOpaqueSystemBarsAppearance; private boolean mEnsureStatusBarContrastWhenTransparent; private boolean mEnsureNavigationBarContrastWhenTransparent; private int mResizeMode; @@ -1855,7 +1861,7 @@ public class ActivityManager { final Icon icon = mIconRes == Resources.ID_NULL ? null : Icon.createWithResource(ActivityThread.currentPackageName(), mIconRes); return new TaskDescription(mLabel, icon, mPrimaryColor, mBackgroundColor, - mStatusBarColor, mNavigationBarColor, 0, false, false, + mStatusBarColor, mNavigationBarColor, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0); } } @@ -1874,7 +1880,7 @@ public class ActivityManager { @Deprecated public TaskDescription(String label, @DrawableRes int iconRes, int colorPrimary) { this(label, Icon.createWithResource(ActivityThread.currentPackageName(), iconRes), - colorPrimary, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0); + colorPrimary, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0); if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) { throw new RuntimeException("A TaskDescription's primary color should be opaque"); } @@ -1892,7 +1898,7 @@ public class ActivityManager { @Deprecated public TaskDescription(String label, @DrawableRes int iconRes) { this(label, Icon.createWithResource(ActivityThread.currentPackageName(), iconRes), - 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0); + 0, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0); } /** @@ -1904,7 +1910,7 @@ public class ActivityManager { */ @Deprecated public TaskDescription(String label) { - this(label, null, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0); + this(label, null, 0, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0); } /** @@ -1914,7 +1920,7 @@ public class ActivityManager { */ @Deprecated public TaskDescription() { - this(null, null, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0); + this(null, null, 0, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0); } /** @@ -1930,7 +1936,7 @@ public class ActivityManager { @Deprecated public TaskDescription(String label, Bitmap icon, int colorPrimary) { this(label, icon != null ? Icon.createWithBitmap(icon) : null, colorPrimary, 0, 0, 0, - 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0); + 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0); if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) { throw new RuntimeException("A TaskDescription's primary color should be opaque"); } @@ -1946,7 +1952,7 @@ public class ActivityManager { */ @Deprecated public TaskDescription(String label, Bitmap icon) { - this(label, icon != null ? Icon.createWithBitmap(icon) : null, 0, 0, 0, 0, 0, false, + this(label, icon != null ? Icon.createWithBitmap(icon) : null, 0, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0); } @@ -1955,6 +1961,7 @@ public class ActivityManager { int colorPrimary, int colorBackground, int statusBarColor, int navigationBarColor, @Appearance int systemBarsAppearance, + @Appearance int topOpaqueSystemBarsAppearance, boolean ensureStatusBarContrastWhenTransparent, boolean ensureNavigationBarContrastWhenTransparent, int resizeMode, int minWidth, int minHeight, int colorBackgroundFloating) { @@ -1965,6 +1972,7 @@ public class ActivityManager { mStatusBarColor = statusBarColor; mNavigationBarColor = navigationBarColor; mSystemBarsAppearance = systemBarsAppearance; + mTopOpaqueSystemBarsAppearance = topOpaqueSystemBarsAppearance; mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent; mEnsureNavigationBarContrastWhenTransparent = ensureNavigationBarContrastWhenTransparent; @@ -1994,6 +2002,7 @@ public class ActivityManager { mStatusBarColor = other.mStatusBarColor; mNavigationBarColor = other.mNavigationBarColor; mSystemBarsAppearance = other.mSystemBarsAppearance; + mTopOpaqueSystemBarsAppearance = other.mTopOpaqueSystemBarsAppearance; mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent; mEnsureNavigationBarContrastWhenTransparent = other.mEnsureNavigationBarContrastWhenTransparent; @@ -2026,6 +2035,9 @@ public class ActivityManager { if (other.mSystemBarsAppearance != 0) { mSystemBarsAppearance = other.mSystemBarsAppearance; } + if (other.mTopOpaqueSystemBarsAppearance != 0) { + mTopOpaqueSystemBarsAppearance = other.mTopOpaqueSystemBarsAppearance; + } mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent; mEnsureNavigationBarContrastWhenTransparent = @@ -2305,6 +2317,14 @@ public class ActivityManager { /** * @hide */ + @Appearance + public int getTopOpaqueSystemBarsAppearance() { + return mTopOpaqueSystemBarsAppearance; + } + + /** + * @hide + */ public void setEnsureStatusBarContrastWhenTransparent( boolean ensureStatusBarContrastWhenTransparent) { mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent; @@ -2320,6 +2340,13 @@ public class ActivityManager { /** * @hide */ + public void setTopOpaqueSystemBarsAppearance(int topOpaqueSystemBarsAppearance) { + mTopOpaqueSystemBarsAppearance = topOpaqueSystemBarsAppearance; + } + + /** + * @hide + */ public boolean getEnsureNavigationBarContrastWhenTransparent() { return mEnsureNavigationBarContrastWhenTransparent; } @@ -2442,6 +2469,7 @@ public class ActivityManager { dest.writeInt(mStatusBarColor); dest.writeInt(mNavigationBarColor); dest.writeInt(mSystemBarsAppearance); + dest.writeInt(mTopOpaqueSystemBarsAppearance); dest.writeBoolean(mEnsureStatusBarContrastWhenTransparent); dest.writeBoolean(mEnsureNavigationBarContrastWhenTransparent); dest.writeInt(mResizeMode); @@ -2466,6 +2494,7 @@ public class ActivityManager { mStatusBarColor = source.readInt(); mNavigationBarColor = source.readInt(); mSystemBarsAppearance = source.readInt(); + mTopOpaqueSystemBarsAppearance = source.readInt(); mEnsureStatusBarContrastWhenTransparent = source.readBoolean(); mEnsureNavigationBarContrastWhenTransparent = source.readBoolean(); mResizeMode = source.readInt(); @@ -2498,7 +2527,8 @@ public class ActivityManager { + " resizeMode: " + ActivityInfo.resizeModeToString(mResizeMode) + " minWidth: " + mMinWidth + " minHeight: " + mMinHeight + " colorBackgrounFloating: " + mColorBackgroundFloating - + " systemBarsAppearance: " + mSystemBarsAppearance; + + " systemBarsAppearance: " + mSystemBarsAppearance + + " topOpaqueSystemBarsAppearance: " + mTopOpaqueSystemBarsAppearance; } @Override @@ -2519,6 +2549,7 @@ public class ActivityManager { result = result * 31 + mStatusBarColor; result = result * 31 + mNavigationBarColor; result = result * 31 + mSystemBarsAppearance; + result = result * 31 + mTopOpaqueSystemBarsAppearance; result = result * 31 + (mEnsureStatusBarContrastWhenTransparent ? 1 : 0); result = result * 31 + (mEnsureNavigationBarContrastWhenTransparent ? 1 : 0); result = result * 31 + mResizeMode; @@ -2542,6 +2573,7 @@ public class ActivityManager { && mStatusBarColor == other.mStatusBarColor && mNavigationBarColor == other.mNavigationBarColor && mSystemBarsAppearance == other.mSystemBarsAppearance + && mTopOpaqueSystemBarsAppearance == other.mTopOpaqueSystemBarsAppearance && mEnsureStatusBarContrastWhenTransparent == other.mEnsureStatusBarContrastWhenTransparent && mEnsureNavigationBarContrastWhenTransparent diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 4c839f1762cb..329fb00c1d9b 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1632,6 +1632,10 @@ public class Notification implements Parcelable private Icon mSmallIcon; @UnsupportedAppUsage private Icon mLargeIcon; + private Icon mAppIcon; + + /** Cache for whether the notification was posted by a headless system app. */ + private Boolean mBelongsToHeadlessSystemApp = null; @UnsupportedAppUsage private String mChannelId; @@ -3079,25 +3083,17 @@ public class Notification implements Parcelable return name.toString(); } } - // If not, try getting the app info from extras. + // If not, try getting the name from the app info. if (context == null) { return null; } - final PackageManager pm = context.getPackageManager(); if (TextUtils.isEmpty(name)) { - if (extras.containsKey(EXTRA_BUILDER_APPLICATION_INFO)) { - final ApplicationInfo info = extras.getParcelable( - EXTRA_BUILDER_APPLICATION_INFO, - ApplicationInfo.class); - if (info != null) { - name = pm.getApplicationLabel(info); - } + ApplicationInfo info = getApplicationInfo(context); + if (info != null) { + final PackageManager pm = context.getPackageManager(); + name = pm.getApplicationLabel(getApplicationInfo(context)); } } - // If that's still empty, use the one from the context directly. - if (TextUtils.isEmpty(name)) { - name = pm.getApplicationLabel(context.getApplicationInfo()); - } // If there's still nothing, ¯\_(ツ)_/¯ if (TextUtils.isEmpty(name)) { return null; @@ -3109,9 +3105,89 @@ public class Notification implements Parcelable } /** + * Whether this notification was posted by a headless system app. + * + * If we don't have enough information to figure this out, this will return false. Therefore, + * false negatives are possible, but false positives should not be. + * * @hide */ - public int loadHeaderAppIconRes(Context context) { + public boolean belongsToHeadlessSystemApp(Context context) { + Trace.beginSection("Notification#belongsToHeadlessSystemApp"); + + try { + if (mBelongsToHeadlessSystemApp != null) { + return mBelongsToHeadlessSystemApp; + } + + if (context == null) { + // Without a valid context, we don't know exactly. Let's assume it doesn't belong to + // a system app, but not cache the value. + return false; + } + + ApplicationInfo info = getApplicationInfo(context); + if (info != null) { + if ((info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { + // It's not a system app at all. + mBelongsToHeadlessSystemApp = false; + } else { + // If there's no launch intent, it's probably a headless app. + final PackageManager pm = context.getPackageManager(); + mBelongsToHeadlessSystemApp = pm.getLaunchIntentForPackage(info.packageName) + == null; + } + } else { + // If for some reason we don't have the app info, we don't know; best assume it's + // not a system app. + return false; + } + return mBelongsToHeadlessSystemApp; + } finally { + Trace.endSection(); + } + } + + /** + * Get the resource ID of the app icon from application info. + * @hide + */ + public int getHeaderAppIconRes(Context context) { + ApplicationInfo info = getApplicationInfo(context); + if (info != null) { + return info.icon; + } + return 0; + } + + /** + * Load the app icon drawable from the package manager. This could result in a binder call. + * @hide + */ + public Drawable loadHeaderAppIcon(Context context) { + Trace.beginSection("Notification#loadHeaderAppIcon"); + + try { + if (context == null) { + Log.e(TAG, "Cannot load the app icon drawable with a null context"); + return null; + } + final PackageManager pm = context.getPackageManager(); + ApplicationInfo info = getApplicationInfo(context); + if (info == null) { + Log.e(TAG, "Cannot load the app icon drawable: no application info"); + return null; + } + return pm.getApplicationIcon(info); + } finally { + Trace.endSection(); + } + } + + /** + * Fetch the application info from the notification, or the context if that isn't available. + */ + private ApplicationInfo getApplicationInfo(Context context) { ApplicationInfo info = null; if (extras.containsKey(EXTRA_BUILDER_APPLICATION_INFO)) { info = extras.getParcelable( @@ -3119,12 +3195,12 @@ public class Notification implements Parcelable ApplicationInfo.class); } if (info == null) { + if (context == null) { + return null; + } info = context.getApplicationInfo(); } - if (info != null) { - return info.icon; - } - return 0; + return info; } /** @@ -4124,6 +4200,55 @@ public class Notification implements Parcelable } /** + * The colored app icon that can replace the small icon in the notification starting in V. + * + * Before using this value, you should first check whether it's actually being used by the + * notification by calling {@link Notification#shouldUseAppIcon()}. + * + * @hide + */ + public Icon getAppIcon() { + if (mAppIcon != null) { + return mAppIcon; + } + // If the app icon hasn't been loaded yet, check if we can load it without a context. + if (extras.containsKey(EXTRA_BUILDER_APPLICATION_INFO)) { + final ApplicationInfo info = extras.getParcelable( + EXTRA_BUILDER_APPLICATION_INFO, + ApplicationInfo.class); + if (info != null) { + int appIconRes = info.icon; + if (appIconRes == 0) { + Log.w(TAG, "Failed to get the app icon: no icon in application info"); + return null; + } + mAppIcon = Icon.createWithResource(info.packageName, appIconRes); + return mAppIcon; + } else { + Log.e(TAG, "Failed to get the app icon: " + + "there's an EXTRA_BUILDER_APPLICATION_INFO in extras but it's null"); + } + } else { + Log.w(TAG, "Failed to get the app icon: no application info in extras"); + } + return null; + } + + /** + * Whether the notification is using the app icon instead of the small icon. + * @hide + */ + public boolean shouldUseAppIcon() { + if (Flags.notificationsUseAppIconInRow()) { + if (belongsToHeadlessSystemApp(/* context = */ null)) { + return false; + } + return getAppIcon() != null; + } + return false; + } + + /** * The large icon shown in this notification's content view. * @see Builder#getLargeIcon() * @see Builder#setLargeIcon(Icon) @@ -6116,16 +6241,30 @@ public class Notification implements Parcelable if (Flags.notificationsUseAppIcon()) { // Override small icon with app icon mN.mSmallIcon = Icon.createWithResource(mContext, - mN.loadHeaderAppIconRes(mContext)); + mN.getHeaderAppIconRes(mContext)); } else if (mN.mSmallIcon == null && mN.icon != 0) { mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon); } - contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon); + boolean usingAppIcon = false; + if (Flags.notificationsUseAppIconInRow() && !mN.belongsToHeadlessSystemApp(mContext)) { + // Use the app icon in the view + int appIconRes = mN.getHeaderAppIconRes(mContext); + if (appIconRes != 0) { + mN.mAppIcon = Icon.createWithResource(mContext, appIconRes); + contentView.setImageViewIcon(R.id.icon, mN.mAppIcon); + usingAppIcon = true; + } else { + Log.w(TAG, "bindSmallIcon: could not get the app icon"); + } + } + if (!usingAppIcon) { + contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon); + } contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel); // Don't change color if we're using the app icon. - if (!Flags.notificationsUseAppIcon()) { + if (!Flags.notificationsUseAppIcon() && !usingAppIcon) { processSmallIconColor(mN.mSmallIcon, contentView, p); } } diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index 55c3bb60e9c7..6edae0b60fd9 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -52,14 +52,32 @@ flag { bug: "281044385" } +# vvv Prototypes for using app icons in notifications vvv + flag { name: "notifications_use_app_icon" namespace: "systemui" - description: "Experiment to replace the small icon in a notification with the app icon." + description: "Experiment to replace the small icon in a notification with the app icon. This includes the status bar, AOD, shelf and notification row itself." + bug: "335211019" +} + +flag { + name: "notifications_use_app_icon_in_row" + namespace: "systemui" + description: "Experiment to replace the small icon in a notification row with the app icon." bug: "335211019" } flag { + name: "notifications_use_monochrome_app_icon" + namespace: "systemui" + description: "Experiment to replace the notification icon in the status bar and shelf with the monochrome app icon, if available." + bug: "335211019" +} + +# ^^^ Prototypes for using app icons in notifications ^^^ + +flag { name: "notification_expansion_optional" namespace: "systemui" description: "Experiment to restore the pre-S behavior where standard notifications are not expandable unless they have actions." diff --git a/core/java/android/app/servertransaction/WindowStateInsetsControlChangeItem.java b/core/java/android/app/servertransaction/WindowStateInsetsControlChangeItem.java index 1c8e497edd0a..ed18a057118c 100644 --- a/core/java/android/app/servertransaction/WindowStateInsetsControlChangeItem.java +++ b/core/java/android/app/servertransaction/WindowStateInsetsControlChangeItem.java @@ -69,7 +69,12 @@ public class WindowStateInsetsControlChangeItem extends WindowStateTransactionIt } instance.setWindow(window); instance.mInsetsState = new InsetsState(insetsState, true /* copySources */); - instance.mActiveControls = new InsetsSourceControl.Array(activeControls); + instance.mActiveControls = new InsetsSourceControl.Array( + activeControls, true /* copyControls */); + // This source control is an extra copy if the client is not local. By setting + // PARCELABLE_WRITE_RETURN_VALUE, the leash will be released at the end of + // SurfaceControl.writeToParcel. + instance.mActiveControls.setParcelableFlags(PARCELABLE_WRITE_RETURN_VALUE); return instance; } diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index e3780edcd7da..f54be00c9e69 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -733,7 +733,7 @@ public final class SQLiteDatabase extends SQLiteClosable { * commits, or is rolled back, either explicitly or by a call to * {@link #yieldIfContendedSafely}. */ - // TODO(340874899) Provide an Executor overload + @SuppressLint("ExecutorRegistration") public void beginTransactionWithListener( @Nullable SQLiteTransactionListener transactionListener) { beginTransaction(transactionListener, true); @@ -763,7 +763,7 @@ public final class SQLiteDatabase extends SQLiteClosable { * transaction begins, commits, or is rolled back, either * explicitly or by a call to {@link #yieldIfContendedSafely}. */ - // TODO(340874899) Provide an Executor overload + @SuppressLint("ExecutorRegistration") public void beginTransactionWithListenerNonExclusive( @Nullable SQLiteTransactionListener transactionListener) { beginTransaction(transactionListener, false); @@ -789,7 +789,6 @@ public final class SQLiteDatabase extends SQLiteClosable { * } * </pre> */ - // TODO(340874899) Provide an Executor overload @SuppressLint("ExecutorRegistration") @FlaggedApi(Flags.FLAG_SQLITE_APIS_35) public void beginTransactionWithListenerReadOnly( diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig index 4284ad09e251..047d1fa4f49a 100644 --- a/core/java/android/hardware/biometrics/flags.aconfig +++ b/core/java/android/hardware/biometrics/flags.aconfig @@ -32,3 +32,10 @@ flag { description: "Feature flag for adding a custom content view API to BiometricPrompt.Builder." bug: "302735104" } + +flag { + name: "mandatory_biometrics" + namespace: "biometrics_framework" + description: "This flag controls whether LSKF fallback is removed from biometric prompt when the phone is outside trusted locations" + bug: "322081563" +} diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 71066ac7ac39..3f9c819cd62f 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -1276,13 +1276,22 @@ public class DreamService extends Service implements Window.Callback { }); } + /** + * Whether or not wake requests will be redirected. + * + * @hide + */ + public boolean getRedirectWake() { + return mOverlayConnection != null && mRedirectWake; + } + private void wakeUp(boolean fromSystem) { if (mDebug) { Slog.v(mTag, "wakeUp(): fromSystem=" + fromSystem + ", mWaking=" + mWaking + ", mFinished=" + mFinished); } - if (!fromSystem && mOverlayConnection != null && mRedirectWake) { + if (!fromSystem && getRedirectWake()) { mOverlayConnection.addConsumer(overlay -> { try { overlay.onWakeRequested(); diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java index 4e5cb58a00b5..588e9e0a3b12 100644 --- a/core/java/android/view/InsetsSourceControl.java +++ b/core/java/android/view/InsetsSourceControl.java @@ -269,22 +269,54 @@ public class InsetsSourceControl implements Parcelable { public Array() { } - public Array(@NonNull Array other) { - mControls = other.mControls; + /** + * @param copyControls whether or not to make a copy of the each {@link InsetsSourceControl} + */ + public Array(@NonNull Array other, boolean copyControls) { + setTo(other, copyControls); } - public Array(Parcel in) { + public Array(@NonNull Parcel in) { readFromParcel(in); } - public void set(@Nullable InsetsSourceControl[] controls) { - mControls = controls; + /** Updates the current Array to the given Array. */ + public void setTo(@NonNull Array other, boolean copyControls) { + set(other.mControls, copyControls); } + /** Updates the current controls to the given controls. */ + public void set(@Nullable InsetsSourceControl[] controls, boolean copyControls) { + if (controls == null || !copyControls) { + mControls = controls; + return; + } + // Make a copy of the array. + mControls = new InsetsSourceControl[controls.length]; + for (int i = mControls.length - 1; i >= 0; i--) { + if (controls[i] != null) { + mControls[i] = new InsetsSourceControl(controls[i]); + } + } + } + + /** Gets the controls. */ public @Nullable InsetsSourceControl[] get() { return mControls; } + /** Sets the given flags to all controls. */ + public void setParcelableFlags(int parcelableFlags) { + if (mControls == null) { + return; + } + for (InsetsSourceControl control : mControls) { + if (control != null) { + control.setParcelableFlags(parcelableFlags); + } + } + } + @Override public int describeContents() { return 0; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 1cb276568244..95c9d7bb72c5 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -23705,12 +23705,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; mPrivateFlags &= ~PFLAG_DIRTY_MASK; - // // For VRR to vote the preferred frame rate - if (sToolkitSetFrameRateReadOnlyFlagValue - && sToolkitFrameRateViewEnablingReadOnlyFlagValue) { - votePreferredFrameRate(); - } - mPrivateFlags4 |= PFLAG4_HAS_DRAWN; // Fast path for layouts with no backgrounds @@ -23727,6 +23721,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, draw(canvas); } } + + // For VRR to vote the preferred frame rate + if (sToolkitSetFrameRateReadOnlyFlagValue + && sToolkitFrameRateViewEnablingReadOnlyFlagValue) { + votePreferredFrameRate(); + } } finally { renderNode.endRecording(); setDisplayListProperties(renderNode); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 139285a44817..b54e052cf538 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2276,6 +2276,33 @@ public final class ViewRootImpl implements ViewParent, requestLayout(); } + /** Handles messages {@link #MSG_INSETS_CONTROL_CHANGED}. */ + private void handleInsetsControlChanged(@NonNull InsetsState insetsState, + @NonNull InsetsSourceControl.Array activeControls) { + final InsetsSourceControl[] controls = activeControls.get(); + + if (mTranslator != null) { + mTranslator.translateInsetsStateInScreenToAppWindow(insetsState); + mTranslator.translateSourceControlsInScreenToAppWindow(controls); + } + + // Deliver state change before control change, such that: + // a) When gaining control, controller can compare with server state to evaluate + // whether it needs to run animation. + // b) When loosing control, controller can restore server state by taking last + // dispatched state as truth. + mInsetsController.onStateChanged(insetsState); + if (mAdded) { + mInsetsController.onControlsChanged(controls); + } else if (controls != null) { + for (InsetsSourceControl control : controls) { + if (control != null) { + control.release(SurfaceControl::release); + } + } + } + } + private final DisplayListener mDisplayListener = new DisplayListener() { @Override public void onDisplayChanged(int displayId) { @@ -6591,24 +6618,11 @@ public final class ViewRootImpl implements ViewParent, break; } case MSG_INSETS_CONTROL_CHANGED: { - SomeArgs args = (SomeArgs) msg.obj; - - // Deliver state change before control change, such that: - // a) When gaining control, controller can compare with server state to evaluate - // whether it needs to run animation. - // b) When loosing control, controller can restore server state by taking last - // dispatched state as truth. - mInsetsController.onStateChanged((InsetsState) args.arg1); - InsetsSourceControl[] controls = (InsetsSourceControl[]) args.arg2; - if (mAdded) { - mInsetsController.onControlsChanged(controls); - } else if (controls != null) { - for (InsetsSourceControl control : controls) { - if (control != null) { - control.release(SurfaceControl::release); - } - } - } + final SomeArgs args = (SomeArgs) msg.obj; + final InsetsState insetsState = (InsetsState) args.arg1; + final InsetsSourceControl.Array activeControls = + (InsetsSourceControl.Array) args.arg2; + handleInsetsControlChanged(insetsState, activeControls); args.recycle(); break; } @@ -9828,25 +9842,9 @@ public final class ViewRootImpl implements ViewParent, mHandler.sendMessage(msg); } - private void dispatchInsetsControlChanged(InsetsState insetsState, - InsetsSourceControl[] activeControls) { - if (Binder.getCallingPid() == android.os.Process.myPid()) { - insetsState = new InsetsState(insetsState, true /* copySource */); - if (activeControls != null) { - for (int i = activeControls.length - 1; i >= 0; i--) { - activeControls[i] = new InsetsSourceControl(activeControls[i]); - } - } - } - if (mTranslator != null) { - mTranslator.translateInsetsStateInScreenToAppWindow(insetsState); - mTranslator.translateSourceControlsInScreenToAppWindow(activeControls); - } - if (insetsState != null && insetsState.isSourceOrDefaultVisible(ID_IME, Type.ime())) { - ImeTracing.getInstance().triggerClientDump("ViewRootImpl#dispatchInsetsControlChanged", - getInsetsController().getHost().getInputMethodManager(), null /* icProto */); - } - SomeArgs args = SomeArgs.obtain(); + private void dispatchInsetsControlChanged(@NonNull InsetsState insetsState, + @NonNull InsetsSourceControl.Array activeControls) { + final SomeArgs args = SomeArgs.obtain(); args.arg1 = insetsState; args.arg2 = activeControls; mHandler.obtainMessage(MSG_INSETS_CONTROL_CHANGED, args).sendToTarget(); @@ -11289,9 +11287,9 @@ public final class ViewRootImpl implements ViewParent, return; } // The the parameters from WindowStateResizeItem are already copied. - final boolean needCopy = + final boolean needsCopy = !isFromResizeItem && (Binder.getCallingPid() == Process.myPid()); - if (needCopy) { + if (needsCopy) { insetsState = new InsetsState(insetsState, true /* copySource */); frames = new ClientWindowFrames(frames); mergedConfiguration = new MergedConfiguration(mergedConfiguration); @@ -11307,10 +11305,32 @@ public final class ViewRootImpl implements ViewParent, final boolean isFromInsetsControlChangeItem = mIsFromTransactionItem; mIsFromTransactionItem = false; final ViewRootImpl viewAncestor = mViewAncestor.get(); - if (viewAncestor != null) { - viewAncestor.dispatchInsetsControlChanged(insetsState, activeControls.get()); + if (viewAncestor == null) { + return; + } + if (insetsState.isSourceOrDefaultVisible(ID_IME, Type.ime())) { + ImeTracing.getInstance().triggerClientDump( + "ViewRootImpl#dispatchInsetsControlChanged", + viewAncestor.getInsetsController().getHost().getInputMethodManager(), + null /* icProto */); + } + // If the UI thread is the same as the current thread that is dispatching + // WindowStateInsetsControlChangeItem, then it can run directly. + if (isFromInsetsControlChangeItem && viewAncestor.mHandler.getLooper() + == ActivityThread.currentActivityThread().getLooper()) { + viewAncestor.handleInsetsControlChanged(insetsState, activeControls); + return; } - // TODO(b/339380439): no need to post if the call is from InsetsControlChangeItem + // The parameters from WindowStateInsetsControlChangeItem are already copied. + final boolean needsCopy = + !isFromInsetsControlChangeItem && (Binder.getCallingPid() == Process.myPid()); + if (needsCopy) { + insetsState = new InsetsState(insetsState, true /* copySource */); + activeControls = new InsetsSourceControl.Array( + activeControls, true /* copyControls */); + } + + viewAncestor.dispatchInsetsControlChanged(insetsState, activeControls); } @Override diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java index 86e5bea46882..1af9387e6fbd 100644 --- a/core/java/android/view/ViewStructure.java +++ b/core/java/android/view/ViewStructure.java @@ -90,6 +90,19 @@ public abstract class ViewStructure { public static final String EXTRA_VIRTUAL_STRUCTURE_TYPE = "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE"; + + /** + * Key used for specifying the version of the view that generated the virtual structure for + * itself and its children + * + * For example, if the virtual structure is generated by a webview of version "104.0.5112.69", + * then the value should be "104.0.5112.69" + * + * @hide + */ + public static final String EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER = + "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER"; + /** * Set the identifier for this view. * diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java index 37b72880dd0c..42fa6ac0407d 100644 --- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java @@ -135,7 +135,7 @@ public class PerfettoProtoLogImpl implements IProtoLog { new DataSourceParams.Builder() .setBufferExhaustedPolicy( DataSourceParams - .PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT) + .PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP) .build(); mDataSource.register(params); this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider; diff --git a/core/java/com/android/internal/widget/NotificationRowIconView.java b/core/java/com/android/internal/widget/NotificationRowIconView.java index 0f4615a12ea2..58bddaecd3e7 100644 --- a/core/java/com/android/internal/widget/NotificationRowIconView.java +++ b/core/java/com/android/internal/widget/NotificationRowIconView.java @@ -59,7 +59,7 @@ public class NotificationRowIconView extends CachingIconView { @Override protected void onFinishInflate() { // If showing the app icon, we don't need background or padding. - if (Flags.notificationsUseAppIcon()) { + if (Flags.notificationsUseAppIcon() || Flags.notificationsUseAppIconInRow()) { setPadding(0, 0, 0, 0); setBackground(null); } diff --git a/core/jni/com_android_internal_content_FileSystemUtils.cpp b/core/jni/com_android_internal_content_FileSystemUtils.cpp index 31f4e641b69e..d426f1240a7f 100644 --- a/core/jni/com_android_internal_content_FileSystemUtils.cpp +++ b/core/jni/com_android_internal_content_FileSystemUtils.cpp @@ -88,7 +88,7 @@ bool punchHoles(const char *filePath, const uint64_t offset, ALOGD("Total number of LOAD segments %zu", programHeaders.size()); ALOGD("Size before punching holes st_blocks: %" PRIu64 - ", st_blksize: %ld, st_size: %" PRIu64 "", + ", st_blksize: %d, st_size: %" PRIu64 "", beforePunch.st_blocks, beforePunch.st_blksize, static_cast<uint64_t>(beforePunch.st_size)); } @@ -193,7 +193,7 @@ bool punchHoles(const char *filePath, const uint64_t offset, ALOGD("lstat64 failed for filePath %s, error:%d", filePath, errno); return false; } - ALOGD("Size after punching holes st_blocks: %" PRIu64 ", st_blksize: %ld, st_size: %" PRIu64 + ALOGD("Size after punching holes st_blocks: %" PRIu64 ", st_blksize: %d, st_size: %" PRIu64 "", afterPunch.st_blocks, afterPunch.st_blksize, static_cast<uint64_t>(afterPunch.st_size)); @@ -271,7 +271,7 @@ bool punchHolesInZip(const char *filePath, uint64_t offset, uint16_t extraFieldL uint64_t blockSize = beforePunch.st_blksize; IF_ALOGD() { ALOGD("Extra field length: %hu, Size before punching holes st_blocks: %" PRIu64 - ", st_blksize: %ld, st_size: %" PRIu64 "", + ", st_blksize: %d, st_size: %" PRIu64 "", extraFieldLen, beforePunch.st_blocks, beforePunch.st_blksize, static_cast<uint64_t>(beforePunch.st_size)); } @@ -346,7 +346,7 @@ bool punchHolesInZip(const char *filePath, uint64_t offset, uint16_t extraFieldL return false; } ALOGD("punchHolesInApk:: Size after punching holes st_blocks: %" PRIu64 - ", st_blksize: %ld, st_size: %" PRIu64 "", + ", st_blksize: %d, st_size: %" PRIu64 "", afterPunch.st_blocks, afterPunch.st_blksize, static_cast<uint64_t>(afterPunch.st_size)); } diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java index 89c2b3cecfef..b972882e68e6 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java @@ -128,7 +128,8 @@ public class ActivityManagerTest extends AndroidTestCase { 0x222222, // colorBackground 0x333333, // statusBarColor 0x444444, // navigationBarColor - 0, // statusBarAppearance + 0x555555, // systemBarsAppeareance + 0x666666, // topOpaqueSystemBarsAppeareance true, // ensureStatusBarContrastWhenTransparent true, // ensureNavigationBarContrastWhenTransparent RESIZE_MODE_RESIZEABLE, // resizeMode @@ -153,7 +154,8 @@ public class ActivityManagerTest extends AndroidTestCase { 0x222222, // colorBackground 0x333333, // statusBarColor 0x444444, // navigationBarColor - 0, // statusBarAppearance + 0x555555, // systemBarsAppeareance + 0x666666, // topOpaqueSystemBarsAppeareance false, // ensureStatusBarContrastWhenTransparent false, // ensureNavigationBarContrastWhenTransparent RESIZE_MODE_UNRESIZEABLE, // resizeMode @@ -169,7 +171,8 @@ public class ActivityManagerTest extends AndroidTestCase { 0x2222222, // colorBackground 0x3333332, // statusBarColor 0x4444442, // navigationBarColor - 0, // statusBarAppearance + 0x5555552, // systemBarsAppeareance + 0x6666662, // topOpaqueSystemBarsAppeareance true, // ensureStatusBarContrastWhenTransparent true, // ensureNavigationBarContrastWhenTransparent RESIZE_MODE_RESIZEABLE, // resizeMode @@ -200,7 +203,8 @@ public class ActivityManagerTest extends AndroidTestCase { 0x222222, // colorBackground 0x333333, // statusBarColor 0x444444, // navigationBarColor - 0, // statusBarAppearance + 0x555555, // systemBarsAppeareance + 0x666666, // topOpaqueSystemBarsAppeareance false, // ensureStatusBarContrastWhenTransparent false, // ensureNavigationBarContrastWhenTransparent RESIZE_MODE_UNRESIZEABLE, // resizeMode @@ -223,7 +227,8 @@ public class ActivityManagerTest extends AndroidTestCase { 0x222222, // colorBackground 0x333333, // statusBarColor 0x444444, // navigationBarColor - 0, // statusBarAppearance + 0x555555, // systemBarsAppeareance + 0x666666, // topOpaqueSystemBarsAppeareance false, // ensureStatusBarContrastWhenTransparent false, // ensureNavigationBarContrastWhenTransparent RESIZE_MODE_UNRESIZEABLE, // resizeMode @@ -256,6 +261,8 @@ public class ActivityManagerTest extends AndroidTestCase { assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor()); assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor()); assertEquals(td1.getSystemBarsAppearance(), td2.getSystemBarsAppearance()); + assertEquals(td1.getTopOpaqueSystemBarsAppearance(), + td2.getTopOpaqueSystemBarsAppearance()); assertEquals(td1.getResizeMode(), td2.getResizeMode()); assertEquals(td1.getMinWidth(), td2.getMinWidth()); assertEquals(td1.getMinHeight(), td2.getMinHeight()); diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index a7f817665f23..94e187aed698 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -1287,6 +1287,31 @@ public class ViewRootImplTest { } @Test + @RequiresFlagsEnabled(FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY) + public void votePreferredFrameRate_velocityVotedAfterOnDraw() throws Throwable { + mView = new View(sContext); + double delta = 0.1; + float pixelsPerSecond = 1000_000; + float expectedFrameRate = 120; + attachViewToWindow(mView); + sInstrumentation.waitForIdleSync(); + ViewRootImpl viewRoot = mView.getViewRootImpl(); + waitForFrameRateCategoryToSettle(mView); + + sInstrumentation.runOnMainSync(() -> { + mView.setFrameContentVelocity(pixelsPerSecond); + mView.invalidate(); + assertEquals(0, viewRoot.getPreferredFrameRate(), delta); + assertEquals(0, viewRoot.getLastPreferredFrameRate(), delta); + runAfterDraw(() -> { + assertEquals(expectedFrameRate, viewRoot.getPreferredFrameRate(), delta); + assertEquals(expectedFrameRate, viewRoot.getLastPreferredFrameRate(), delta); + }); + }); + waitForAfterDraw(); + } + + @Test public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() { mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR); ShellIdentityUtils.invokeWithShellPermissions(() -> { diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java index 6ca6517abbb0..dc022b4afd3b 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java @@ -69,7 +69,7 @@ public class TransitionUtil { /** Returns {@code true} if the transition is opening or closing mode. */ public static boolean isOpenOrCloseMode(@TransitionInfo.TransitionMode int mode) { - return isOpeningMode(mode) || mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK; + return isOpeningMode(mode) || isClosingMode(mode); } /** Returns {@code true} if the transition is opening mode. */ @@ -77,6 +77,11 @@ public class TransitionUtil { return mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT; } + /** Returns {@code true} if the transition is closing mode. */ + public static boolean isClosingMode(@TransitionInfo.TransitionMode int mode) { + return mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK; + } + /** Returns {@code true} if the transition has a display change. */ public static boolean hasDisplayChange(@NonNull TransitionInfo info) { for (int i = info.getChanges().size() - 1; i >= 0; --i) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt index ec204714c341..7ade9876d28a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt @@ -23,13 +23,13 @@ import android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BA val TaskInfo.isTransparentCaptionBarAppearance: Boolean get() { - val appearance = taskDescription?.systemBarsAppearance ?: 0 + val appearance = taskDescription?.topOpaqueSystemBarsAppearance ?: 0 return (appearance and APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND) != 0 } val TaskInfo.isLightCaptionBarAppearance: Boolean get() { - val appearance = taskDescription?.systemBarsAppearance ?: 0 + val appearance = taskDescription?.topOpaqueSystemBarsAppearance ?: 0 return (appearance and APPEARANCE_LIGHT_CAPTION_BARS) != 0 } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt index 285e5b6a04a5..51b291c0b7a4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt @@ -39,7 +39,7 @@ import org.junit.runner.RunWith class DesktopModeUiEventLoggerTest : ShellTestCase() { private lateinit var uiEventLoggerFake: UiEventLoggerFake private lateinit var logger: DesktopModeUiEventLogger - private val instanceIdSequence = InstanceIdSequence(10) + private val instanceIdSequence = InstanceIdSequence(/* instanceIdMax */ 1 shl 20) @Before diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java index 3ca9b57e03fd..a731e5394bdf 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java @@ -228,7 +228,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { public void updateRelayoutParams_freeformAndTransparentAppearance_allowsInputFallthrough() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); - taskInfo.taskDescription.setSystemBarsAppearance( + taskInfo.taskDescription.setTopOpaqueSystemBarsAppearance( APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND); final RelayoutParams relayoutParams = new RelayoutParams(); @@ -246,7 +246,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { public void updateRelayoutParams_freeformButOpaqueAppearance_disallowsInputFallthrough() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); - taskInfo.taskDescription.setSystemBarsAppearance(0); + taskInfo.taskDescription.setTopOpaqueSystemBarsAppearance(0); final RelayoutParams relayoutParams = new RelayoutParams(); DesktopModeWindowDecoration.updateRelayoutParams( diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp index f1ee3256dbee..eecc741a3bbb 100644 --- a/libs/input/MouseCursorController.cpp +++ b/libs/input/MouseCursorController.cpp @@ -165,6 +165,15 @@ void MouseCursorController::setStylusHoverMode(bool stylusHoverMode) { } } +void MouseCursorController::setSkipScreenshot(bool skip) { + std::scoped_lock lock(mLock); + if (mLocked.skipScreenshot == skip) { + return; + } + mLocked.skipScreenshot = skip; + updatePointerLocked(); +} + void MouseCursorController::reloadPointerResources(bool getAdditionalMouseResources) { std::scoped_lock lock(mLock); @@ -352,6 +361,7 @@ void MouseCursorController::updatePointerLocked() REQUIRES(mLock) { mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER); mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY); mLocked.pointerSprite->setDisplayId(mLocked.viewport.displayId); + mLocked.pointerSprite->setSkipScreenshot(mLocked.skipScreenshot); if (mLocked.pointerAlpha > 0) { mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha); diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h index dc7e8ca16c8a..78f6413ff111 100644 --- a/libs/input/MouseCursorController.h +++ b/libs/input/MouseCursorController.h @@ -53,6 +53,9 @@ public: void setDisplayViewport(const DisplayViewport& viewport, bool getAdditionalMouseResources); void setStylusHoverMode(bool stylusHoverMode); + // Set/Unset flag to hide the mouse cursor on the mirrored display + void setSkipScreenshot(bool skip); + void updatePointerIcon(PointerIconStyle iconId); void setCustomPointerIcon(const SpriteIcon& icon); void reloadPointerResources(bool getAdditionalMouseResources); @@ -94,6 +97,7 @@ private: PointerIconStyle requestedPointerType; PointerIconStyle resolvedPointerType; + bool skipScreenshot{false}; bool animating{false}; } mLocked GUARDED_BY(mLock); diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index cca1b07c3118..11b27a214984 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -286,13 +286,16 @@ void PointerController::setCustomPointerIcon(const SpriteIcon& icon) { mCursorController.setCustomPointerIcon(icon); } -void PointerController::setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) { +void PointerController::setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId displayId) { std::scoped_lock lock(getLock()); - if (skip) { - mLocked.displaysToSkipScreenshot.insert(displayId); - } else { - mLocked.displaysToSkipScreenshot.erase(displayId); - } + mLocked.displaysToSkipScreenshot.insert(displayId); + mCursorController.setSkipScreenshot(true); +} + +void PointerController::clearSkipScreenshotFlags() { + std::scoped_lock lock(getLock()); + mLocked.displaysToSkipScreenshot.clear(); + mCursorController.setSkipScreenshot(false); } void PointerController::doInactivityTimeout() { diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index c6430f7f36ff..4d1e1d733cc1 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -66,7 +66,8 @@ public: void clearSpots() override; void updatePointerIcon(PointerIconStyle iconId) override; void setCustomPointerIcon(const SpriteIcon& icon) override; - void setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) override; + void setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId displayId) override; + void clearSkipScreenshotFlags() override; virtual void setInactivityTimeout(InactivityTimeout inactivityTimeout); void doInactivityTimeout(); diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp index 2dcb1f1d1650..cbef68e2eb8f 100644 --- a/libs/input/tests/PointerController_test.cpp +++ b/libs/input/tests/PointerController_test.cpp @@ -183,12 +183,16 @@ private: MyLooper() : Looper(false) {} ~MyLooper() = default; }; - sp<MyLooper> mLooper; std::thread mThread; + +protected: + sp<MyLooper> mLooper; }; -PointerControllerTest::PointerControllerTest() : mPointerSprite(new NiceMock<MockSprite>), - mLooper(new MyLooper), mThread(&PointerControllerTest::loopThread, this) { +PointerControllerTest::PointerControllerTest() + : mPointerSprite(new NiceMock<MockSprite>), + mThread(&PointerControllerTest::loopThread, this), + mLooper(new MyLooper) { mSpriteController.reset(new NiceMock<MockSpriteController>(mLooper)); mPolicy = new MockPointerControllerPolicyInterface(); @@ -339,7 +343,7 @@ TEST_F(PointerControllerTest, updatesSkipScreenshotFlagForTouchSpots) { testing::Mock::VerifyAndClearExpectations(testSpotSprite.get()); // Marking the display to skip screenshot should update sprite as well - mPointerController->setSkipScreenshot(ui::LogicalDisplayId::DEFAULT, true); + mPointerController->setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId::DEFAULT); EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(true)); // Update spots to sync state with sprite @@ -348,13 +352,53 @@ TEST_F(PointerControllerTest, updatesSkipScreenshotFlagForTouchSpots) { testing::Mock::VerifyAndClearExpectations(testSpotSprite.get()); // Reset flag and verify again - mPointerController->setSkipScreenshot(ui::LogicalDisplayId::DEFAULT, false); + mPointerController->clearSkipScreenshotFlags(); EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(false)); mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits, ui::LogicalDisplayId::DEFAULT); testing::Mock::VerifyAndClearExpectations(testSpotSprite.get()); } +class PointerControllerSkipScreenshotFlagTest + : public PointerControllerTest, + public testing::WithParamInterface<PointerControllerInterface::ControllerType> {}; + +TEST_P(PointerControllerSkipScreenshotFlagTest, updatesSkipScreenshotFlag) { + sp<MockSprite> testPointerSprite(new NiceMock<MockSprite>); + EXPECT_CALL(*mSpriteController, createSprite).WillOnce(Return(testPointerSprite)); + + // Create a pointer controller + mPointerController = + PointerController::create(mPolicy, mLooper, *mSpriteController, GetParam()); + ensureDisplayViewportIsSet(ui::LogicalDisplayId::DEFAULT); + + // By default skip screenshot flag is not set for the sprite + EXPECT_CALL(*testPointerSprite, setSkipScreenshot).With(testing::Args<0>(false)); + + // Update pointer to sync state with sprite + mPointerController->setPosition(100, 100); + testing::Mock::VerifyAndClearExpectations(testPointerSprite.get()); + + // Marking the controller to skip screenshot should update pointer sprite + mPointerController->setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId::DEFAULT); + EXPECT_CALL(*testPointerSprite, setSkipScreenshot).With(testing::Args<0>(true)); + + // Update pointer to sync state with sprite + mPointerController->move(10, 10); + testing::Mock::VerifyAndClearExpectations(testPointerSprite.get()); + + // Reset flag and verify again + mPointerController->clearSkipScreenshotFlags(); + EXPECT_CALL(*testPointerSprite, setSkipScreenshot).With(testing::Args<0>(false)); + mPointerController->move(10, 10); + testing::Mock::VerifyAndClearExpectations(testPointerSprite.get()); +} + +INSTANTIATE_TEST_SUITE_P(PointerControllerSkipScreenshotFlagTest, + PointerControllerSkipScreenshotFlagTest, + testing::Values(PointerControllerInterface::ControllerType::MOUSE, + PointerControllerInterface::ControllerType::STYLUS)); + class PointerControllerWindowInfoListenerTest : public Test {}; TEST_F(PointerControllerWindowInfoListenerTest, diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java index 70462effaa54..442ccdcddb2b 100644 --- a/media/java/android/media/session/MediaController.java +++ b/media/java/android/media/session/MediaController.java @@ -141,10 +141,9 @@ public final class MediaController { } try { return mSessionBinder.sendMediaButton(mContext.getPackageName(), keyEvent); - } catch (RemoteException e) { - // System is dead. =( + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } - return false; } /** @@ -155,9 +154,8 @@ public final class MediaController { public @Nullable PlaybackState getPlaybackState() { try { return mSessionBinder.getPlaybackState(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling getPlaybackState.", e); - return null; + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -169,9 +167,8 @@ public final class MediaController { public @Nullable MediaMetadata getMetadata() { try { return mSessionBinder.getMetadata(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling getMetadata.", e); - return null; + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -185,10 +182,9 @@ public final class MediaController { try { ParceledListSlice list = mSessionBinder.getQueue(); return list == null ? null : list.getList(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling getQueue.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } - return null; } /** @@ -197,10 +193,9 @@ public final class MediaController { public @Nullable CharSequence getQueueTitle() { try { return mSessionBinder.getQueueTitle(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling getQueueTitle", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } - return null; } /** @@ -209,10 +204,9 @@ public final class MediaController { public @Nullable Bundle getExtras() { try { return mSessionBinder.getExtras(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling getExtras", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } - return null; } /** @@ -232,9 +226,8 @@ public final class MediaController { public int getRatingType() { try { return mSessionBinder.getRatingType(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling getRatingType.", e); - return Rating.RATING_NONE; + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -246,10 +239,9 @@ public final class MediaController { public long getFlags() { try { return mSessionBinder.getFlags(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling getFlags.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } - return 0; } /** Returns the current playback info for this session. */ @@ -271,10 +263,9 @@ public final class MediaController { public @Nullable PendingIntent getSessionActivity() { try { return mSessionBinder.getLaunchPendingIntent(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling getPendingIntent.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } - return null; } /** @@ -304,8 +295,8 @@ public final class MediaController { // AppOpsManager usages. mSessionBinder.setVolumeTo(mContext.getPackageName(), mContext.getOpPackageName(), value, flags); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling setVolumeTo.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -329,8 +320,8 @@ public final class MediaController { // AppOpsManager usages. mSessionBinder.adjustVolume(mContext.getPackageName(), mContext.getOpPackageName(), direction, flags); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling adjustVolumeBy.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -395,8 +386,8 @@ public final class MediaController { } try { mSessionBinder.sendCommand(mContext.getPackageName(), command, args, cb); - } catch (RemoteException e) { - Log.d(TAG, "Dead object in sendCommand.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -409,8 +400,8 @@ public final class MediaController { if (mPackageName == null) { try { mPackageName = mSessionBinder.getPackageName(); - } catch (RemoteException e) { - Log.d(TAG, "Dead object in getPackageName.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } return mPackageName; @@ -430,8 +421,8 @@ public final class MediaController { // Get info from the connected session. try { mSessionInfo = mSessionBinder.getSessionInfo(); - } catch (RemoteException e) { - Log.d(TAG, "Dead object in getSessionInfo.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } if (mSessionInfo == null) { @@ -454,8 +445,8 @@ public final class MediaController { if (mTag == null) { try { mTag = mSessionBinder.getTag(); - } catch (RemoteException e) { - Log.d(TAG, "Dead object in getTag.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } return mTag; @@ -485,8 +476,8 @@ public final class MediaController { try { mSessionBinder.registerCallback(mContext.getPackageName(), mCbStub); mCbRegistered = true; - } catch (RemoteException e) { - Log.e(TAG, "Dead object in registerCallback", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } } @@ -504,8 +495,8 @@ public final class MediaController { if (mCbRegistered && mCallbacks.size() == 0) { try { mSessionBinder.unregisterCallback(mCbStub); - } catch (RemoteException e) { - Log.e(TAG, "Dead object in removeCallbackLocked"); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } mCbRegistered = false; } @@ -641,8 +632,8 @@ public final class MediaController { public void prepare() { try { mSessionBinder.prepare(mContext.getPackageName()); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling prepare.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -665,8 +656,8 @@ public final class MediaController { } try { mSessionBinder.prepareFromMediaId(mContext.getPackageName(), mediaId, extras); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling prepare(" + mediaId + ").", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -691,8 +682,8 @@ public final class MediaController { } try { mSessionBinder.prepareFromSearch(mContext.getPackageName(), query, extras); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling prepare(" + query + ").", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -715,8 +706,8 @@ public final class MediaController { } try { mSessionBinder.prepareFromUri(mContext.getPackageName(), uri, extras); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling prepare(" + uri + ").", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -726,8 +717,8 @@ public final class MediaController { public void play() { try { mSessionBinder.play(mContext.getPackageName()); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling play.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -745,8 +736,8 @@ public final class MediaController { } try { mSessionBinder.playFromMediaId(mContext.getPackageName(), mediaId, extras); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling play(" + mediaId + ").", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -767,8 +758,8 @@ public final class MediaController { } try { mSessionBinder.playFromSearch(mContext.getPackageName(), query, extras); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling play(" + query + ").", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -786,8 +777,8 @@ public final class MediaController { } try { mSessionBinder.playFromUri(mContext.getPackageName(), uri, extras); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling play(" + uri + ").", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -798,8 +789,8 @@ public final class MediaController { public void skipToQueueItem(long id) { try { mSessionBinder.skipToQueueItem(mContext.getPackageName(), id); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling skipToItem(" + id + ").", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -810,8 +801,8 @@ public final class MediaController { public void pause() { try { mSessionBinder.pause(mContext.getPackageName()); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling pause.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -822,8 +813,8 @@ public final class MediaController { public void stop() { try { mSessionBinder.stop(mContext.getPackageName()); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling stop.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -835,8 +826,8 @@ public final class MediaController { public void seekTo(long pos) { try { mSessionBinder.seekTo(mContext.getPackageName(), pos); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling seekTo.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -847,8 +838,8 @@ public final class MediaController { public void fastForward() { try { mSessionBinder.fastForward(mContext.getPackageName()); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling fastForward.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -858,8 +849,8 @@ public final class MediaController { public void skipToNext() { try { mSessionBinder.next(mContext.getPackageName()); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling next.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -870,8 +861,8 @@ public final class MediaController { public void rewind() { try { mSessionBinder.rewind(mContext.getPackageName()); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling rewind.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -881,8 +872,8 @@ public final class MediaController { public void skipToPrevious() { try { mSessionBinder.previous(mContext.getPackageName()); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling previous.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -896,8 +887,8 @@ public final class MediaController { public void setRating(Rating rating) { try { mSessionBinder.rate(mContext.getPackageName(), rating); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling rate.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -914,8 +905,8 @@ public final class MediaController { } try { mSessionBinder.setPlaybackSpeed(mContext.getPackageName(), speed); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling setPlaybackSpeed.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -949,8 +940,8 @@ public final class MediaController { } try { mSessionBinder.sendCustomAction(mContext.getPackageName(), action, args); - } catch (RemoteException e) { - Log.d(TAG, "Dead object in sendCustomAction.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } } diff --git a/packages/SettingsLib/IllustrationPreference/Android.bp b/packages/SettingsLib/IllustrationPreference/Android.bp index c3a91a20c339..cd8f584953aa 100644 --- a/packages/SettingsLib/IllustrationPreference/Android.bp +++ b/packages/SettingsLib/IllustrationPreference/Android.bp @@ -47,6 +47,7 @@ java_aconfig_library { aconfig_declarations: "settingslib_illustrationpreference_flags", min_sdk_version: "30", + sdk_version: "system_current", apex_available: [ "//apex_available:platform", diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml index ab049042b5f9..470cdeea149b 100644 --- a/packages/SettingsLib/res/values/dimens.xml +++ b/packages/SettingsLib/res/values/dimens.xml @@ -32,7 +32,7 @@ <!-- Usage graph dimens --> <dimen name="usage_graph_margin_top_bottom">9dp</dimen> - <dimen name="usage_graph_labels_width">56dp</dimen> + <dimen name="usage_graph_labels_width">60dp</dimen> <dimen name="usage_graph_divider_size">1dp</dimen> diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt index 869fb7f4043c..70811951c9b7 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt @@ -81,7 +81,7 @@ class LocalMediaRepositoryImpl( localMediaManager.unregisterCallback(callback) } } - .shareIn(coroutineScope, SharingStarted.WhileSubscribed(), replay = 0) + .shareIn(coroutineScope, SharingStarted.Eagerly, replay = 0) override val currentConnectedDevice: StateFlow<MediaDevice?> = merge(devicesChanges, mediaDevicesUpdates) @@ -89,8 +89,8 @@ class LocalMediaRepositoryImpl( .onStart { emit(localMediaManager.currentConnectedDevice) } .stateIn( coroutineScope, - SharingStarted.WhileSubscribed(), - localMediaManager.currentConnectedDevice + SharingStarted.Eagerly, + localMediaManager.currentConnectedDevice, ) private sealed interface DevicesUpdate { diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java index ca1e4c10d339..e4898daf3cbf 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java @@ -27,11 +27,11 @@ import android.support.test.uiautomator.UiDevice; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.bedstead.enterprise.annotations.EnsureHasNoWorkProfile; +import com.android.bedstead.enterprise.annotations.EnsureHasWorkProfile; import com.android.bedstead.harrier.BedsteadJUnit4; import com.android.bedstead.harrier.DeviceState; -import com.android.bedstead.harrier.annotations.EnsureHasNoWorkProfile; import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser; -import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile; import com.android.bedstead.harrier.annotations.RequireFeature; import com.android.bedstead.harrier.annotations.RequireRunOnInitialUser; import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser; diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 7ce8f98a4cbb..63a52d624ca7 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -177,16 +177,9 @@ flag { } flag { - name: "notification_throttle_hun" - namespace: "systemui" - description: "During notification avalanche, throttle HUNs showing in fast succession." - bug: "307288824" -} - -flag { name: "notification_avalanche_throttle_hun" namespace: "systemui" - description: "(currently unused) During notification avalanche, throttle HUNs showing in fast succession." + description: "During notification avalanche, throttle HUNs showing in fast succession." bug: "307288824" } @@ -997,6 +990,13 @@ flag { } flag { + name: "glanceable_hub_fullscreen_swipe" + namespace: "systemui" + description: "Increase swipe area for gestures to bring in glanceable hub" + bug: "339665673" +} + +flag { name: "glanceable_hub_shortcut_button" namespace: "systemui" description: "Shows a button over the dream and lock screen to open the glanceable hub" diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/platform/DensityAwareComposeView.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/platform/DensityAwareComposeView.kt deleted file mode 100644 index dff8753fd880..000000000000 --- a/packages/SystemUI/compose/core/src/com/android/compose/ui/platform/DensityAwareComposeView.kt +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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.compose.ui.platform - -import android.content.Context -import android.content.res.Configuration -import android.util.AttributeSet -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.ui.platform.AbstractComposeView - -/** - * A ComposeView that recreates its composition if the display size or font scale was changed. - * - * TODO(b/317317814): Remove this workaround. - */ -class DensityAwareComposeView(context: Context) : OpenComposeView(context) { - private var lastDensityDpi: Int = -1 - private var lastFontScale: Float = -1f - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - - val configuration = context.resources.configuration - lastDensityDpi = configuration.densityDpi - lastFontScale = configuration.fontScale - } - - override fun dispatchConfigurationChanged(newConfig: Configuration) { - super.dispatchConfigurationChanged(newConfig) - - // If the density or font scale changed, we dispose then recreate the composition. Note that - // we do this here after dispatching the new configuration to children (instead of doing - // this in onConfigurationChanged()) because the new configuration should first be - // dispatched to the AndroidComposeView that holds the current density before we recreate - // the composition. - val densityDpi = newConfig.densityDpi - val fontScale = newConfig.fontScale - if (densityDpi != lastDensityDpi || fontScale != lastFontScale) { - lastDensityDpi = densityDpi - lastFontScale = fontScale - - disposeComposition() - if (isAttachedToWindow) { - createComposition() - } - } - } -} - -/** A fork of [androidx.compose.ui.platform.ComposeView] that is open and can be subclassed. */ -open class OpenComposeView -internal constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : - AbstractComposeView(context, attrs, defStyleAttr) { - - private val content = mutableStateOf<(@Composable () -> Unit)?>(null) - - @Suppress("RedundantVisibilityModifier") - protected override var shouldCreateCompositionOnAttachedToWindow: Boolean = false - - @Composable - override fun Content() { - content.value?.invoke() - } - - override fun getAccessibilityClassName(): CharSequence { - return javaClass.name - } - - /** - * Set the Jetpack Compose UI content for this view. Initial composition will occur when the - * view becomes attached to a window or when [createComposition] is called, whichever comes - * first. - */ - fun setContent(content: @Composable () -> Unit) { - shouldCreateCompositionOnAttachedToWindow = true - this.content.value = content - if (isAttachedToWindow) { - createComposition() - } - } -} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index feb1f5b17bef..a90f82eda1af 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -21,6 +21,8 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.animation.scene.CommunalSwipeDetector +import com.android.compose.animation.scene.DefaultSwipeDetector import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.ElementMatcher @@ -35,6 +37,7 @@ import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.observableTransitionState import com.android.compose.animation.scene.transitions import com.android.systemui.Flags +import com.android.systemui.Flags.glanceableHubFullscreenSwipe import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.communal.ui.compose.extensions.allowGestures @@ -108,6 +111,8 @@ fun CommunalContainer( ) } + val detector = remember { CommunalSwipeDetector() } + DisposableEffect(state) { val dataSource = SceneTransitionLayoutDataSource(state, coroutineScope) dataSourceDelegator.setDelegate(dataSource) @@ -121,13 +126,25 @@ fun CommunalContainer( onDispose { viewModel.setTransitionState(null) } } + val swipeSourceDetector = + if (glanceableHubFullscreenSwipe()) { + detector + } else { + FixedSizeEdgeDetector(dimensionResource(id = R.dimen.communal_gesture_initiation_width)) + } + + val swipeDetector = + if (glanceableHubFullscreenSwipe()) { + detector + } else { + DefaultSwipeDetector + } + SceneTransitionLayout( state = state, modifier = modifier.fillMaxSize(), - swipeSourceDetector = - FixedSizeEdgeDetector( - dimensionResource(id = R.dimen.communal_gesture_initiation_width) - ), + swipeSourceDetector = swipeSourceDetector, + swipeDetector = swipeDetector, ) { scene( CommunalScenes.Blank, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt index 271eb9601dbd..fbf91b702fb9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt @@ -68,9 +68,7 @@ fun VolumeSlider( state.a11yClickDescription?.let { customActions = listOf( - CustomAccessibilityAction( - it, - ) { + CustomAccessibilityAction(it) { onIconTapped() true } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/CommunalSwipeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/CommunalSwipeDetector.kt new file mode 100644 index 000000000000..7be34cabfaf8 --- /dev/null +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/CommunalSwipeDetector.kt @@ -0,0 +1,56 @@ +/* + * 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.compose.animation.scene + +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.ui.input.pointer.PointerInputChange +import androidx.compose.ui.input.pointer.positionChange +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntSize +import kotlin.math.abs + +private const val TRAVEL_RATIO_THRESHOLD = .5f + +/** + * {@link CommunalSwipeDetector} provides an implementation of {@link SwipeDetector} and {@link + * SwipeSourceDetector} to enable fullscreen swipe handling to transition to and from the glanceable + * hub. + */ +class CommunalSwipeDetector(private var lastDirection: SwipeSource? = null) : + SwipeSourceDetector, SwipeDetector { + override fun source( + layoutSize: IntSize, + position: IntOffset, + density: Density, + orientation: Orientation + ): SwipeSource? { + return lastDirection + } + + override fun detectSwipe(change: PointerInputChange): Boolean { + if (change.positionChange().x > 0) { + lastDirection = Edge.Left + } else { + lastDirection = Edge.Right + } + + // Determine whether the ratio of the distance traveled horizontally to the distance + // traveled vertically exceeds the threshold. + return abs(change.positionChange().x / change.positionChange().y) > TRAVEL_RATIO_THRESHOLD + } +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt index 0fc0053ce4a1..3cc8431cd87e 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt @@ -72,6 +72,7 @@ internal fun Modifier.multiPointerDraggable( enabled: () -> Boolean, startDragImmediately: (startedPosition: Offset) -> Boolean, onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController, + swipeDetector: SwipeDetector = DefaultSwipeDetector, ): Modifier = this.then( MultiPointerDraggableElement( @@ -79,6 +80,7 @@ internal fun Modifier.multiPointerDraggable( enabled, startDragImmediately, onDragStarted, + swipeDetector, ) ) @@ -88,6 +90,7 @@ private data class MultiPointerDraggableElement( private val startDragImmediately: (startedPosition: Offset) -> Boolean, private val onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController, + private val swipeDetector: SwipeDetector, ) : ModifierNodeElement<MultiPointerDraggableNode>() { override fun create(): MultiPointerDraggableNode = MultiPointerDraggableNode( @@ -95,6 +98,7 @@ private data class MultiPointerDraggableElement( enabled = enabled, startDragImmediately = startDragImmediately, onDragStarted = onDragStarted, + swipeDetector = swipeDetector, ) override fun update(node: MultiPointerDraggableNode) { @@ -102,6 +106,7 @@ private data class MultiPointerDraggableElement( node.enabled = enabled node.startDragImmediately = startDragImmediately node.onDragStarted = onDragStarted + node.swipeDetector = swipeDetector } } @@ -111,6 +116,7 @@ internal class MultiPointerDraggableNode( var startDragImmediately: (startedPosition: Offset) -> Boolean, var onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController, + var swipeDetector: SwipeDetector = DefaultSwipeDetector, ) : PointerInputModifierNode, DelegatingNode(), @@ -199,6 +205,7 @@ internal class MultiPointerDraggableNode( onDragCancel = { controller -> controller.onStop(velocity = 0f, canChangeScene = true) }, + swipeDetector = swipeDetector ) } catch (exception: CancellationException) { // If the coroutine scope is active, we can just restart the drag cycle. @@ -226,7 +233,8 @@ internal class MultiPointerDraggableNode( (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController, onDrag: (controller: DragController, change: PointerInputChange, dragAmount: Float) -> Unit, onDragEnd: (controller: DragController) -> Unit, - onDragCancel: (controller: DragController) -> Unit + onDragCancel: (controller: DragController) -> Unit, + swipeDetector: SwipeDetector, ) { // Wait for a consumable event in [PointerEventPass.Main] pass val consumablePointer = awaitConsumableEvent().changes.first() @@ -238,8 +246,10 @@ internal class MultiPointerDraggableNode( consumablePointer } else { val onSlopReached = { change: PointerInputChange, over: Float -> - change.consume() - overSlop = over + if (swipeDetector.detectSwipe(change)) { + change.consume() + overSlop = over + } } // TODO(b/291055080): Replace by await[Orientation]PointerSlopOrCancellation once it diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index 11e711ace971..cf8c5841f797 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -55,6 +55,7 @@ fun SceneTransitionLayout( state: SceneTransitionLayoutState, modifier: Modifier = Modifier, swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector, + swipeDetector: SwipeDetector = DefaultSwipeDetector, @FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f, scenes: SceneTransitionLayoutScope.() -> Unit, ) { @@ -62,6 +63,7 @@ fun SceneTransitionLayout( state, modifier, swipeSourceDetector, + swipeDetector, transitionInterceptionThreshold, onLayoutImpl = null, scenes, @@ -95,6 +97,7 @@ fun SceneTransitionLayout( transitions: SceneTransitions, modifier: Modifier = Modifier, swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector, + swipeDetector: SwipeDetector = DefaultSwipeDetector, @FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f, enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED, scenes: SceneTransitionLayoutScope.() -> Unit, @@ -111,6 +114,7 @@ fun SceneTransitionLayout( state, modifier, swipeSourceDetector, + swipeDetector, transitionInterceptionThreshold, scenes, ) @@ -467,6 +471,7 @@ internal fun SceneTransitionLayoutForTesting( state: SceneTransitionLayoutState, modifier: Modifier = Modifier, swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector, + swipeDetector: SwipeDetector = DefaultSwipeDetector, transitionInterceptionThreshold: Float = 0f, onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)? = null, scenes: SceneTransitionLayoutScope.() -> Unit, @@ -502,5 +507,5 @@ internal fun SceneTransitionLayoutForTesting( layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold } - layoutImpl.Content(modifier) + layoutImpl.Content(modifier, swipeDetector) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt index 7856498aa365..c614265e2ae1 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt @@ -185,14 +185,14 @@ internal class SceneTransitionLayoutImpl( } @Composable - internal fun Content(modifier: Modifier) { + internal fun Content(modifier: Modifier, swipeDetector: SwipeDetector) { Box( modifier // Handle horizontal and vertical swipes on this layout. // Note: order here is important and will give a slight priority to the vertical // swipes. - .swipeToScene(horizontalDraggableHandler) - .swipeToScene(verticalDraggableHandler) + .swipeToScene(horizontalDraggableHandler, swipeDetector) + .swipeToScene(verticalDraggableHandler, swipeDetector) .then(LayoutElement(layoutImpl = this)) ) { LookaheadScope { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeDetector.kt new file mode 100644 index 000000000000..54ee78366875 --- /dev/null +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeDetector.kt @@ -0,0 +1,40 @@ +/* + * 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.compose.animation.scene + +import androidx.compose.runtime.Stable +import androidx.compose.ui.input.pointer.PointerInputChange + +/** {@link SwipeDetector} helps determine whether a swipe gestured has occurred. */ +@Stable +interface SwipeDetector { + /** + * Invoked on changes to pointer input. Returns {@code true} if a swipe has been recognized, + * {@code false} otherwise. + */ + fun detectSwipe(change: PointerInputChange): Boolean +} + +val DefaultSwipeDetector = PassthroughSwipeDetector() + +/** An {@link SwipeDetector} implementation that recognizes a swipe on any input. */ +class PassthroughSwipeDetector : SwipeDetector { + override fun detectSwipe(change: PointerInputChange): Boolean { + // Simply accept all changes as a swipe + return true + } +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt index b618369c2369..171e2430c004 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt @@ -31,14 +31,18 @@ import androidx.compose.ui.unit.IntSize * Configures the swipeable behavior of a [SceneTransitionLayout] depending on the current state. */ @Stable -internal fun Modifier.swipeToScene(draggableHandler: DraggableHandlerImpl): Modifier { - return this.then(SwipeToSceneElement(draggableHandler)) +internal fun Modifier.swipeToScene( + draggableHandler: DraggableHandlerImpl, + swipeDetector: SwipeDetector +): Modifier { + return this.then(SwipeToSceneElement(draggableHandler, swipeDetector)) } private data class SwipeToSceneElement( val draggableHandler: DraggableHandlerImpl, + val swipeDetector: SwipeDetector ) : ModifierNodeElement<SwipeToSceneNode>() { - override fun create(): SwipeToSceneNode = SwipeToSceneNode(draggableHandler) + override fun create(): SwipeToSceneNode = SwipeToSceneNode(draggableHandler, swipeDetector) override fun update(node: SwipeToSceneNode) { node.draggableHandler = draggableHandler @@ -47,6 +51,7 @@ private data class SwipeToSceneElement( private class SwipeToSceneNode( draggableHandler: DraggableHandlerImpl, + swipeDetector: SwipeDetector, ) : DelegatingNode(), PointerInputModifierNode { private val delegate = delegate( @@ -55,6 +60,7 @@ private class SwipeToSceneNode( enabled = ::enabled, startDragImmediately = ::startDragImmediately, onDragStarted = draggableHandler::onDragStarted, + swipeDetector = swipeDetector, ) ) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt index aa6d1130fc2a..4bb643f8b89e 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.input.pointer.AwaitPointerEventScope import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalViewConfiguration @@ -346,4 +347,69 @@ class MultiPointerDraggableTest { continueDraggingDown() assertThat(stopped).isTrue() } + + @Test + fun multiPointerSwipeDetectorInteraction() { + val size = 200f + val middle = Offset(size / 2f, size / 2f) + + var started = false + + var capturedChange: PointerInputChange? = null + var swipeConsume = false + + var touchSlop = 0f + rule.setContent { + touchSlop = LocalViewConfiguration.current.touchSlop + Box( + Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() }) + .multiPointerDraggable( + orientation = Orientation.Vertical, + enabled = { true }, + startDragImmediately = { false }, + swipeDetector = + object : SwipeDetector { + override fun detectSwipe(change: PointerInputChange): Boolean { + capturedChange = change + return swipeConsume + } + }, + onDragStarted = { _, _, _ -> + started = true + object : DragController { + override fun onDrag(delta: Float) {} + + override fun onStop(velocity: Float, canChangeScene: Boolean) {} + } + }, + ) + ) {} + } + + fun startDraggingDown() { + rule.onRoot().performTouchInput { + down(middle) + moveBy(Offset(0f, touchSlop)) + } + } + + fun continueDraggingDown() { + rule.onRoot().performTouchInput { moveBy(Offset(0f, touchSlop)) } + } + + startDraggingDown() + assertThat(capturedChange).isNotNull() + capturedChange = null + assertThat(started).isFalse() + + swipeConsume = true + continueDraggingDown() + assertThat(capturedChange).isNotNull() + capturedChange = null + + continueDraggingDown() + assertThat(capturedChange).isNull() + + assertThat(started).isTrue() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt index e39ad4f0b405..a676c7db4290 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt @@ -25,15 +25,18 @@ import android.view.Display import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.brightness.data.model.LinearBrightness +import com.android.systemui.brightness.shared.model.LinearBrightness import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope +import com.android.systemui.log.core.FakeLogBuffer +import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.testKosmos import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -74,6 +77,8 @@ class ScreenBrightnessDisplayManagerRepositoryTest : SysuiTestCase() { ScreenBrightnessDisplayManagerRepository( displayId, displayManager, + FakeLogBuffer.Factory.create(), + mock<TableLogBuffer>(), kosmos.applicationCoroutineScope, kosmos.testDispatcher, ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt index 33c44f8a331e..b6616bf0c8de 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt @@ -20,13 +20,16 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.display.BrightnessUtils import com.android.systemui.SysuiTestCase -import com.android.systemui.brightness.data.model.LinearBrightness import com.android.systemui.brightness.data.repository.fakeScreenBrightnessRepository import com.android.systemui.brightness.data.repository.screenBrightnessRepository -import com.android.systemui.brightness.shared.GammaBrightness +import com.android.systemui.brightness.shared.model.GammaBrightness +import com.android.systemui.brightness.shared.model.LinearBrightness import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testScope +import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent @@ -41,7 +44,14 @@ class ScreenBrightnessInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() - private val underTest = ScreenBrightnessInteractor(kosmos.screenBrightnessRepository) + private val underTest = + with(kosmos) { + ScreenBrightnessInteractor( + screenBrightnessRepository, + applicationCoroutineScope, + mock<TableLogBuffer>() + ) + } @Test fun gammaBrightness() = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt index 0058ee4a9c4e..8402676dbd6b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt @@ -20,15 +20,16 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.display.BrightnessUtils import com.android.systemui.SysuiTestCase -import com.android.systemui.brightness.data.model.LinearBrightness import com.android.systemui.brightness.data.repository.fakeScreenBrightnessRepository import com.android.systemui.brightness.domain.interactor.brightnessPolicyEnforcementInteractor import com.android.systemui.brightness.domain.interactor.screenBrightnessInteractor -import com.android.systemui.brightness.shared.GammaBrightness +import com.android.systemui.brightness.shared.model.GammaBrightness +import com.android.systemui.brightness.shared.model.LinearBrightness import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.testKosmos @@ -52,6 +53,7 @@ class BrightnessSliderViewModelTest : SysuiTestCase() { BrightnessSliderViewModel( screenBrightnessInteractor, brightnessPolicyEnforcementInteractor, + applicationCoroutineScope, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt index 723f6a2bfff4..9300db9a24c8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt @@ -16,29 +16,40 @@ package com.android.systemui.dreams.homecontrols import android.app.Activity +import android.content.Intent +import android.service.controls.ControlsProviderService.CONTROLS_SURFACE_ACTIVITY_PANEL +import android.service.controls.ControlsProviderService.CONTROLS_SURFACE_DREAM +import android.service.controls.ControlsProviderService.EXTRA_CONTROLS_SURFACE +import android.window.TaskFragmentInfo import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.controls.settings.FakeControlsSettingsRepository import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope -import com.android.systemui.log.core.FakeLogBuffer.Factory.Companion.create import com.android.systemui.log.logcatLogBuffer import com.android.systemui.testKosmos -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.whenever import com.android.systemui.util.wakelock.WakeLockFake import com.google.common.truth.Truth.assertThat import java.util.Optional +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.Mockito.never -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations - +import org.mockito.kotlin.any +import org.mockito.kotlin.argThat +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class HomeControlsDreamServiceTest : SysuiTestCase() { @@ -46,31 +57,38 @@ class HomeControlsDreamServiceTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder - private lateinit var fakeWakeLock: WakeLockFake - - @Mock private lateinit var taskFragmentComponentFactory: TaskFragmentComponent.Factory - @Mock private lateinit var taskFragmentComponent: TaskFragmentComponent - @Mock private lateinit var activity: Activity + private val fakeWakeLock = WakeLockFake() + private val fakeWakeLockBuilder by lazy { + WakeLockFake.Builder(context).apply { setWakeLock(fakeWakeLock) } + } + + private val taskFragmentComponent = mock<TaskFragmentComponent>() + private val activity = mock<Activity>() + private val onCreateCallback = argumentCaptor<(TaskFragmentInfo) -> Unit>() + private val onInfoChangedCallback = argumentCaptor<(TaskFragmentInfo) -> Unit>() + private val hideCallback = argumentCaptor<() -> Unit>() + private val dreamServiceDelegate = + mock<DreamServiceDelegate> { on { getActivity(any()) } doReturn activity } + + private val taskFragmentComponentFactory = + mock<TaskFragmentComponent.Factory> { + on { + create( + activity = eq(activity), + onCreateCallback = onCreateCallback.capture(), + onInfoChangedCallback = onInfoChangedCallback.capture(), + hide = hideCallback.capture(), + ) + } doReturn taskFragmentComponent + } - private lateinit var underTest: HomeControlsDreamService + private val underTest: HomeControlsDreamService by lazy { buildService() } @Before - fun setup() = - with(kosmos) { - MockitoAnnotations.initMocks(this@HomeControlsDreamServiceTest) - whenever(taskFragmentComponentFactory.create(any(), any(), any(), any())) - .thenReturn(taskFragmentComponent) - - fakeWakeLock = WakeLockFake() - fakeWakeLockBuilder = WakeLockFake.Builder(context) - fakeWakeLockBuilder.setWakeLock(fakeWakeLock) - - whenever(controlsComponent.getControlsListingController()) - .thenReturn(Optional.of(controlsListingController)) - - underTest = buildService { activity } - } + fun setup() { + whenever(kosmos.controlsComponent.getControlsListingController()) + .thenReturn(Optional.of(kosmos.controlsListingController)) + } @Test fun testOnAttachedToWindowCreatesTaskFragmentComponent() = @@ -90,9 +108,12 @@ class HomeControlsDreamServiceTest : SysuiTestCase() { @Test fun testNotCreatingTaskFragmentComponentWhenActivityIsNull() = testScope.runTest { - underTest = buildService { null } + val serviceWithNullActivity = + buildService( + mock<DreamServiceDelegate> { on { getActivity(underTest) } doReturn null } + ) - underTest.onAttachedToWindow() + serviceWithNullActivity.onAttachedToWindow() verify(taskFragmentComponentFactory, never()).create(any(), any(), any(), any()) } @@ -102,6 +123,7 @@ class HomeControlsDreamServiceTest : SysuiTestCase() { underTest.onAttachedToWindow() assertThat(fakeWakeLock.isHeld).isTrue() } + @Test fun testDetachWindow_wakeLockCanBeReleased() = testScope.runTest { @@ -112,14 +134,60 @@ class HomeControlsDreamServiceTest : SysuiTestCase() { assertThat(fakeWakeLock.isHeld).isFalse() } - private fun buildService(activityProvider: DreamActivityProvider): HomeControlsDreamService = + @Test + fun testFinishesDreamWithoutRestartingActivityWhenNotRedirectingWakes() = + testScope.runTest { + whenever(dreamServiceDelegate.redirectWake(any())).thenReturn(false) + underTest.onAttachedToWindow() + onCreateCallback.firstValue.invoke(mock<TaskFragmentInfo>()) + verify(taskFragmentComponent, times(1)).startActivityInTaskFragment(intentMatcher()) + + // Task fragment becomes empty + onInfoChangedCallback.firstValue.invoke( + mock<TaskFragmentInfo> { on { isEmpty } doReturn true } + ) + advanceUntilIdle() + // Dream is finished and activity is not restarted + verify(taskFragmentComponent, times(1)).startActivityInTaskFragment(intentMatcher()) + verify(dreamServiceDelegate, never()).wakeUp(any()) + verify(dreamServiceDelegate).finish(any()) + } + + @Test + fun testRestartsActivityWhenRedirectingWakes() = + testScope.runTest { + whenever(dreamServiceDelegate.redirectWake(any())).thenReturn(true) + underTest.onAttachedToWindow() + onCreateCallback.firstValue.invoke(mock<TaskFragmentInfo>()) + verify(taskFragmentComponent, times(1)).startActivityInTaskFragment(intentMatcher()) + + // Task fragment becomes empty + onInfoChangedCallback.firstValue.invoke( + mock<TaskFragmentInfo> { on { isEmpty } doReturn true } + ) + advanceUntilIdle() + // Activity is restarted instead of finishing the dream. + verify(taskFragmentComponent, times(2)).startActivityInTaskFragment(intentMatcher()) + verify(dreamServiceDelegate).wakeUp(any()) + verify(dreamServiceDelegate, never()).finish(any()) + } + + private fun intentMatcher() = + argThat<Intent> { + getIntExtra(EXTRA_CONTROLS_SURFACE, CONTROLS_SURFACE_ACTIVITY_PANEL) == + CONTROLS_SURFACE_DREAM + } + + private fun buildService( + activityProvider: DreamServiceDelegate = dreamServiceDelegate + ): HomeControlsDreamService = with(kosmos) { return HomeControlsDreamService( controlsSettingsRepository = FakeControlsSettingsRepository(), taskFragmentFactory = taskFragmentComponentFactory, homeControlsComponentInteractor = homeControlsComponentInteractor, - fakeWakeLockBuilder, - dreamActivityProvider = activityProvider, + wakeLockBuilder = fakeWakeLockBuilder, + dreamServiceDelegate = activityProvider, bgDispatcher = testDispatcher, logBuffer = logcatLogBuffer("HomeControlsDreamServiceTest") ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt index d630a2f64c5f..6c5001ab9415 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt @@ -136,20 +136,20 @@ class FromAodTransitionInteractorTest : SysuiTestCase() { @Test @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) - fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromAod_nonDismissableKeyguard() = + fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromAod_nonDismissibleKeyguard() = testScope.runTest { powerInteractor.onCameraLaunchGestureDetected() powerInteractor.setAwakeForTest() advanceTimeBy(100) // account for debouncing - // We should head back to GONE since we started there. + // We should head to OCCLUDED because keyguard is not dismissible. assertThat(transitionRepository) .startedTransition(from = KeyguardState.AOD, to = KeyguardState.OCCLUDED) } @Test @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) - fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromAod_dismissableKeyguard() = + fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromAod_dismissibleKeyguard() = testScope.runTest { kosmos.fakeKeyguardRepository.setKeyguardDismissible(true) powerInteractor.onCameraLaunchGestureDetected() @@ -188,6 +188,7 @@ class FromAodTransitionInteractorTest : SysuiTestCase() { ) // Detect a power gesture and then wake up. + kosmos.fakeKeyguardRepository.setKeyguardDismissible(true) reset(transitionRepository) powerInteractor.onCameraLaunchGestureDetected() powerInteractor.setAwakeForTest() @@ -355,6 +356,7 @@ class FromAodTransitionInteractorTest : SysuiTestCase() { ) // Detect a power gesture and then wake up. + kosmos.fakeKeyguardRepository.setKeyguardDismissible(true) reset(transitionRepository) powerInteractor.onCameraLaunchGestureDetected() powerInteractor.setAwakeForTest() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt index bfc777509c7b..612f2e73e4bb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt @@ -56,13 +56,13 @@ import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.se import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.testKosmos import junit.framework.Assert.assertEquals -import kotlin.test.Test import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before +import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.reset import org.mockito.Mockito.spy @@ -230,6 +230,7 @@ class FromDozingTransitionInteractorTest : SysuiTestCase() { ) // Detect a power gesture and then wake up. + kosmos.fakeKeyguardRepository.setKeyguardDismissible(true) reset(transitionRepository) powerInteractor.onCameraLaunchGestureDetected() powerInteractor.setAwakeForTest() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt index 78a116737349..5068f6830fa1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt @@ -350,7 +350,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { } @Test - fun quickAffordance_updateOncePerShadeExpansion() = + fun quickAffordance_doNotSendUpdatesWhileShadeExpandingAndStillHidden() = testScope.runTest { val shadeExpansion = MutableStateFlow(0f) whenever(shadeInteractor.anyExpansion).thenReturn(shadeExpansion) @@ -365,7 +365,9 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { shadeExpansion.value = i / 10f } - assertThat(collectedValue.size).isEqualTo(initialSize + 1) + assertThat(collectedValue[0]) + .isInstanceOf(KeyguardQuickAffordanceModel.Hidden::class.java) + assertThat(collectedValue.size).isEqualTo(initialSize) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt index 04c270d07b0a..ad24a711e9b7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.BurnInModel import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition import com.android.systemui.kosmos.testScope @@ -104,6 +105,7 @@ class KeyguardIndicationAreaViewModelTest : SysuiTestCase() { burnInInteractor = burnInInteractor, shortcutsCombinedViewModel = shortcutsCombinedViewModel, configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()), + keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt index 37d472169ae5..7ebebd7afa91 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt @@ -203,6 +203,21 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() { .containsExactlyElementsIn(DEFAULT_TILES.toTileSpecs() + startingTiles) } + @Test + fun prependDefault_noChangesWhenInRetail() = + testScope.runTest { + val user = 0 + retailModeRepository.setRetailMode(true) + val startingTiles = "a" + storeTilesForUser(startingTiles, user) + + runCurrent() + underTest.prependDefault(user) + runCurrent() + + assertThat(loadTilesForUser(user)).isEqualTo(startingTiles) + } + private fun TestScope.storeTilesForUser(specs: String, forUser: Int) { secureSettings.putStringForUser(SETTING, specs, forUser) runCurrent() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt index 58fc10917d44..b12fbc2066a2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt @@ -327,6 +327,32 @@ class UserTileSpecRepositoryTest : SysuiTestCase() { assertThat(loadTiles()).isEqualTo(expected) } + @Test + fun setTilesWithRepeats_onlyDistinctTiles() = + testScope.runTest { + val tilesToSet = "a,b,c,a,d,b".toTileSpecs() + val expected = "a,b,c,d" + + val tiles by collectLastValue(underTest.tiles()) + underTest.setTiles(tilesToSet) + + assertThat(tiles).isEqualTo(expected.toTileSpecs()) + assertThat(loadTiles()).isEqualTo(expected) + } + + @Test + fun prependDefaultTwice_doesntAddMoreTiles() = + testScope.runTest { + val tiles by collectLastValue(underTest.tiles()) + underTest.setTiles(listOf(TileSpec.create("a"))) + + underTest.prependDefault() + val currentTiles = tiles!! + underTest.prependDefault() + + assertThat(tiles).isEqualTo(currentTiles) + } + private fun getDefaultTileSpecs(): List<TileSpec> { return defaultTilesRepository.defaultTiles } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt index 1c73fe2b305d..6ad4b317b94c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt @@ -49,6 +49,7 @@ import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger import com.android.systemui.qs.tiles.di.NewQSTileFactory import com.android.systemui.qs.toProto +import com.android.systemui.retail.data.repository.FakeRetailModeRepository import com.android.systemui.settings.UserTracker import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.util.mockito.any @@ -85,6 +86,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { private val pipelineFlags = QSPipelineFlagsRepository() private val tileLifecycleManagerFactory = TLMFactory() private val minimumTilesRepository = MinimumTilesFixedRepository() + private val retailModeRepository = FakeRetailModeRepository() @Mock private lateinit var customTileStatePersister: CustomTileStatePersister @@ -118,6 +120,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { installedTilesComponentRepository = installedTilesPackageRepository, userRepository = userRepository, minimumTilesRepository = minimumTilesRepository, + retailModeRepository = retailModeRepository, customTileStatePersister = customTileStatePersister, tileFactory = tileFactory, newQSTileFactory = { newQSTileFactory }, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt index 260189d401d2..e8ad038f8fbc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt @@ -34,6 +34,8 @@ import com.android.systemui.qs.pipeline.data.repository.MinimumTilesFixedReposit import com.android.systemui.qs.pipeline.data.repository.fakeDefaultTilesRepository import com.android.systemui.qs.pipeline.data.repository.fakeMinimumTilesRepository import com.android.systemui.qs.pipeline.data.repository.fakeRestoreRepository +import com.android.systemui.qs.pipeline.data.repository.fakeRetailModeRepository +import com.android.systemui.qs.pipeline.data.repository.fakeTileSpecRepository import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.qsTileFactory import com.android.systemui.settings.fakeUserTracker @@ -138,6 +140,19 @@ class NoLowNumberOfTilesTest : SysuiTestCase() { } } + @Test + fun inRetailMode_onlyOneTile_noPrependDefault() = + with(kosmos) { + testScope.runTest { + fakeRetailModeRepository.setRetailMode(true) + fakeTileSpecRepository.setTiles(0, listOf(goodTile)) + val tiles by collectLastValue(currentTilesInteractor.currentTiles) + runCurrent() + + assertThat(tiles!!.map { it.spec }).isEqualTo(listOf(goodTile)) + } + } + private fun tileCreator(spec: String): QSTile? { return if (spec.contains("OEM")) { null // We don't know how to create OEM spec tiles diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt index 63f19fbdfed9..6b5d07282a08 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt @@ -78,7 +78,7 @@ class AvalancheControllerTest : SysuiTestCase() { // Initialize AvalancheController and TestableHeadsUpManager during setUp instead of // declaration, where mocks are null - mAvalancheController = AvalancheController(dumpManager) + mAvalancheController = AvalancheController(dumpManager, mUiEventLoggerFake) testableHeadsUpManager = TestableHeadsUpManager( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java index 3bfc046e46b4..88bef91d043f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java @@ -38,6 +38,7 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.app.PendingIntent; import android.app.Person; +import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.FlagsParameterization; import android.testing.TestableLooper; @@ -147,7 +148,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { @Override public void SysuiSetup() throws Exception { super.SysuiSetup(); - mAvalancheController = new AvalancheController(dumpManager); + mAvalancheController = new AvalancheController(dumpManager, mUiEventLoggerFake); } @Test @@ -610,7 +611,31 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { } @Test - public void testPinEntry_logsPeek() { + @EnableFlags(NotificationThrottleHun.FLAG_NAME) + public void testPinEntry_logsPeek_throttleEnabled() { + final BaseHeadsUpManager hum = createHeadsUpManager(); + + // Needs full screen intent in order to be pinned + final BaseHeadsUpManager.HeadsUpEntry entryToPin = hum.new HeadsUpEntry( + HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext)); + + // Note: the standard way to show a notification would be calling showNotification rather + // than onAlertEntryAdded. However, in practice showNotification in effect adds + // the notification and then updates it; in order to not log twice, the entry needs + // to have a functional ExpandableNotificationRow that can keep track of whether it's + // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit. + hum.onEntryAdded(entryToPin); + + assertEquals(2, mUiEventLoggerFake.numLogs()); + assertEquals(AvalancheController.ThrottleEvent.SHOWN.getId(), + mUiEventLoggerFake.eventId(0)); + assertEquals(BaseHeadsUpManager.NotificationPeekEvent.NOTIFICATION_PEEK.getId(), + mUiEventLoggerFake.eventId(1)); + } + + @Test + @DisableFlags(NotificationThrottleHun.FLAG_NAME) + public void testPinEntry_logsPeek_throttleDisabled() { final BaseHeadsUpManager hum = createHeadsUpManager(); // Needs full screen intent in order to be pinned diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java index 9feb914c56e9..200e92e4370b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java @@ -167,7 +167,7 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest { mContext.getOrCreateTestableResources().addOverride( R.integer.ambient_notification_extension_time, 500); - mAvalancheController = new AvalancheController(dumpManager); + mAvalancheController = new AvalancheController(dumpManager, mUiEventLogger); } @Test diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions.xml b/packages/SystemUI/res-keyguard/layout/footer_actions.xml deleted file mode 100644 index 4a2a1cb9dc6d..000000000000 --- a/packages/SystemUI/res-keyguard/layout/footer_actions.xml +++ /dev/null @@ -1,29 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -** Copyright 2022, 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. ---> - -<!-- Action buttons for footer in QS/QQS, containing settings button, power off button etc --> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="@dimen/footer_actions_height" - android:elevation="@dimen/qs_panel_elevation" - android:paddingTop="@dimen/qs_footer_actions_top_padding" - android:paddingBottom="@dimen/qs_footer_actions_bottom_padding" - android:background="@drawable/qs_footer_actions_background" - android:gravity="center_vertical|end" - android:layout_gravity="bottom" -/>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_icon_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_icon_button.xml deleted file mode 100644 index fad41c822ec0..000000000000 --- a/packages/SystemUI/res-keyguard/layout/footer_actions_icon_button.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2022 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. ---> -<com.android.systemui.statusbar.AlphaOptimizedFrameLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="@dimen/qs_footer_action_button_size" - android:layout_height="@dimen/qs_footer_action_button_size" - android:visibility="gone"> - <ImageView - android:id="@+id/icon" - android:layout_width="@dimen/qs_footer_icon_size" - android:layout_height="@dimen/qs_footer_icon_size" - android:layout_gravity="center" - android:scaleType="centerInside" /> -</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml deleted file mode 100644 index c09607d19bdd..000000000000 --- a/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml +++ /dev/null @@ -1,39 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2022 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. ---> -<com.android.systemui.statusbar.AlphaOptimizedFrameLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="@dimen/qs_footer_action_button_size" - android:layout_height="@dimen/qs_footer_action_button_size" - android:background="@drawable/qs_footer_action_circle" - android:visibility="gone"> - <TextView - android:id="@+id/number" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="@style/TextAppearance.QS.SecurityFooter" - android:layout_gravity="center" - android:textColor="?attr/onShadeInactiveVariant" - android:textSize="18sp"/> - <ImageView - android:id="@+id/new_dot" - android:layout_width="12dp" - android:layout_height="12dp" - android:scaleType="fitCenter" - android:layout_gravity="bottom|end" - android:src="@drawable/fgs_dot" - android:contentDescription="@string/fgs_dot_content_description" /> -</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml deleted file mode 100644 index 1c31f1da0681..000000000000 --- a/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml +++ /dev/null @@ -1,66 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2022 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. ---> -<com.android.systemui.animation.view.LaunchableLinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="0dp" - android:layout_height="@dimen/qs_security_footer_single_line_height" - android:layout_weight="1" - android:orientation="horizontal" - android:paddingHorizontal="@dimen/qs_footer_padding" - android:gravity="center_vertical" - android:layout_marginEnd="@dimen/qs_footer_action_inset" - android:background="@drawable/qs_security_footer_background" - android:visibility="gone"> - <ImageView - android:id="@+id/icon" - android:layout_width="@dimen/qs_footer_icon_size" - android:layout_height="@dimen/qs_footer_icon_size" - android:gravity="start" - android:layout_marginEnd="12dp" - android:contentDescription="@null" - android:src="@drawable/ic_info_outline" - android:tint="?attr/onSurfaceVariant" /> - - <TextView - android:id="@+id/text" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:maxLines="1" - android:ellipsize="end" - android:textAppearance="@style/TextAppearance.QS.SecurityFooter" - android:textColor="?attr/onSurfaceVariant"/> - - <ImageView - android:id="@+id/new_dot" - android:layout_width="12dp" - android:layout_height="12dp" - android:scaleType="fitCenter" - android:src="@drawable/fgs_dot" - android:contentDescription="@string/fgs_dot_content_description" - /> - - <ImageView - android:id="@+id/chevron_icon" - android:layout_width="@dimen/qs_footer_icon_size" - android:layout_height="@dimen/qs_footer_icon_size" - android:layout_marginStart="8dp" - android:contentDescription="@null" - android:src="@*android:drawable/ic_chevron_end" - android:autoMirrored="true" - android:tint="?attr/onSurfaceVariant" /> -</com.android.systemui.animation.view.LaunchableLinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/color/qs_footer_power_button_overlay_color.xml b/packages/SystemUI/res/color/qs_footer_power_button_overlay_color.xml deleted file mode 100644 index a8abd793bd00..000000000000 --- a/packages/SystemUI/res/color/qs_footer_power_button_overlay_color.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2023 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. - --> - -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_pressed="true" android:color="?attr/onShadeActive" android:alpha="0.12" /> - <item android:state_hovered="true" android:color="?attr/onShadeActive" android:alpha="0.09" /> - <item android:color="@color/transparent" /> -</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/fgs_dot.xml b/packages/SystemUI/res/drawable/fgs_dot.xml deleted file mode 100644 index 0881d7c5c2b5..000000000000 --- a/packages/SystemUI/res/drawable/fgs_dot.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -** Copyright 2022, 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. ---> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:shape="oval" - android:width="12dp" - android:height="12dp"> - <solid android:color="?attr/tertiary" /> -</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml deleted file mode 100644 index 4a5d4af96497..000000000000 --- a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml +++ /dev/null @@ -1,37 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2022 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. - --> -<inset xmlns:android="http://schemas.android.com/apk/res/android" - android:inset="@dimen/qs_footer_action_inset"> - <ripple - android:color="?android:attr/colorControlHighlight"> - <item android:id="@android:id/mask"> - <!-- We make this shape a rounded rectangle instead of a oval so that it can animate --> - <!-- properly into an app/dialog. --> - <shape android:shape="rectangle"> - <solid android:color="@android:color/white"/> - <corners android:radius="@dimen/qs_footer_action_corner_radius"/> - </shape> - </item> - <item> - <shape android:shape="rectangle"> - <solid android:color="?attr/shadeInactive"/> - <corners android:radius="@dimen/qs_footer_action_corner_radius"/> - </shape> - </item> - - </ripple> -</inset>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml deleted file mode 100644 index 47a2965bcfac..000000000000 --- a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml +++ /dev/null @@ -1,43 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2022 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. - --> -<inset xmlns:android="http://schemas.android.com/apk/res/android" - android:inset="@dimen/qs_footer_action_inset"> - <ripple - android:color="?android:attr/colorControlHighlight"> - <item android:id="@android:id/mask"> - <!-- We make this shape a rounded rectangle instead of a oval so that it can animate --> - <!-- properly into an app/dialog. --> - <shape android:shape="rectangle"> - <solid android:color="@android:color/white"/> - <corners android:radius="@dimen/qs_footer_action_corner_radius"/> - </shape> - </item> - <item> - <shape android:shape="rectangle"> - <solid android:color="?attr/shadeActive"/> - <corners android:radius="@dimen/qs_footer_action_corner_radius"/> - </shape> - </item> - <item> - <shape android:shape="rectangle"> - <solid android:color="@color/qs_footer_power_button_overlay_color"/> - <corners android:radius="@dimen/qs_footer_action_corner_radius"/> - </shape> - </item> - - </ripple> -</inset>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/qs_security_footer_background.xml b/packages/SystemUI/res/drawable/qs_security_footer_background.xml deleted file mode 100644 index 0b0055b1f020..000000000000 --- a/packages/SystemUI/res/drawable/qs_security_footer_background.xml +++ /dev/null @@ -1,37 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2021 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. - --> -<inset xmlns:android="http://schemas.android.com/apk/res/android" - android:insetTop="@dimen/qs_footer_action_inset" - android:insetBottom="@dimen/qs_footer_action_inset" - > - <ripple - android:color="?android:attr/colorControlHighlight"> - <item android:id="@android:id/mask"> - <shape android:shape="rectangle"> - <solid android:color="@android:color/white"/> - <corners android:radius="@dimen/qs_security_footer_corner_radius"/> - </shape> - </item> - <item> - <shape android:shape="rectangle"> - <stroke android:width="1dp" - android:color="?attr/shadeInactive"/> - <corners android:radius="@dimen/qs_security_footer_corner_radius"/> - </shape> - </item> - </ripple> -</inset>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/rounded_bg_full_large_radius.xml b/packages/SystemUI/res/drawable/rounded_bg_full_large_radius.xml deleted file mode 100644 index 29a014a713f7..000000000000 --- a/packages/SystemUI/res/drawable/rounded_bg_full_large_radius.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2021 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. ---> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:shape="rectangle"> - <solid android:color="?androidprv:attr/colorAccentPrimary" /> - <corners android:radius="40dp" /> -</shape> diff --git a/packages/SystemUI/res/layout/people_space_activity.xml b/packages/SystemUI/res/layout/people_space_activity.xml deleted file mode 100644 index f45cc7c464d5..000000000000 --- a/packages/SystemUI/res/layout/people_space_activity.xml +++ /dev/null @@ -1,23 +0,0 @@ -<!-- - ~ Copyright (C) 2020 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. - --> -<FrameLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/container" - android:layout_width="match_parent" - android:layout_height="match_parent"> - <!-- The content of people_space_activity_(no|with)_conversations.xml will be added here at - runtime depending on the number of conversations to show. --> -</FrameLayout> diff --git a/packages/SystemUI/res/layout/people_space_activity_list_divider.xml b/packages/SystemUI/res/layout/people_space_activity_list_divider.xml deleted file mode 100644 index 3b9fb3be3814..000000000000 --- a/packages/SystemUI/res/layout/people_space_activity_list_divider.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2021 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. ---> -<View - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="2dp" - android:background="?android:attr/colorBackground" /> diff --git a/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml b/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml deleted file mode 100644 index a97c90c5e8ac..000000000000 --- a/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml +++ /dev/null @@ -1,79 +0,0 @@ -<!-- - ~ Copyright (C) 2021 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. - --> -<RelativeLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:id="@+id/top_level_no_conversations" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:padding="24dp" - android:clipToOutline="true"> - <TextView - android:id="@+id/select_conversation_title" - android:gravity="center" - android:text="@string/select_conversation_title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_centerHorizontal="true" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" - android:textColor="?android:attr/textColorPrimary" - android:textSize="24sp" - android:layout_alignParentTop="true" /> - - <TextView - android:id="@+id/select_conversation" - android:gravity="center" - android:text="@string/no_conversations_text" - android:layout_width="match_parent" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" - android:textColor="?android:attr/textColorPrimary" - android:textSize="16sp" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:padding="24dp" - android:layout_marginTop="26dp" - android:layout_below="@id/select_conversation_title"/> - - <Button - style="?android:attr/buttonBarButtonStyle" - android:id="@+id/got_it_button" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:background="@drawable/rounded_bg_full_large_radius" - android:text="@string/got_it" - android:textColor="?androidprv:attr/textColorOnAccent" - android:layout_marginBottom="60dp" - android:layout_alignParentBottom="true" /> - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_above="@id/got_it_button" - android:layout_below="@id/select_conversation" - android:layout_centerInParent="true" - android:clipToOutline="true"> - <LinearLayout - android:id="@+id/widget_initial_layout" - android:layout_width="200dp" - android:layout_height="100dp" - android:layout_gravity="center" - android:background="@drawable/rounded_bg_full_large_radius" - android:layout_above="@id/got_it_button"> - <include layout="@layout/people_space_placeholder_layout" /> - </LinearLayout> - </LinearLayout> -</RelativeLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml b/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml deleted file mode 100644 index 2384963c44db..000000000000 --- a/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml +++ /dev/null @@ -1,115 +0,0 @@ -<!-- - ~ Copyright (C) 2022 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. - --> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:id="@+id/top_level_with_conversations" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:padding="8dp"> - <TextView - android:id="@+id/select_conversation_title" - android:text="@string/select_conversation_title" - android:gravity="center" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" - android:textColor="?android:attr/textColorPrimary" - android:textSize="24sp"/> - - <TextView - android:id="@+id/select_conversation" - android:text="@string/select_conversation_text" - android:gravity="center" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" - android:textColor="?android:attr/textColorPrimary" - android:textSize="16sp" - android:paddingVertical="24dp" - android:paddingHorizontal="48dp"/> - - <androidx.core.widget.NestedScrollView - android:id="@+id/scroll_view" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <LinearLayout - android:id="@+id/scroll_layout" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="16dp" - android:orientation="vertical"> - - <LinearLayout - android:id="@+id/priority" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="35dp"> - <TextView - android:id="@+id/priority_header" - android:text="@string/priority_conversations" - android:layout_width="wrap_content" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title" - android:textColor="?androidprv:attr/colorAccentPrimaryVariant" - android:textSize="14sp" - android:paddingStart="16dp" - android:layout_height="wrap_content"/> - - <LinearLayout - android:id="@+id/priority_tiles" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="10dp" - android:orientation="vertical" - android:background="@drawable/rounded_bg_full_large_radius" - android:clipToOutline="true"> - </LinearLayout> - </LinearLayout> - - <LinearLayout - android:id="@+id/recent" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - <TextView - android:id="@+id/recent_header" - android:gravity="start" - android:text="@string/recent_conversations" - android:layout_width="wrap_content" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title" - android:textColor="?androidprv:attr/colorAccentPrimaryVariant" - android:textSize="14sp" - android:paddingStart="16dp" - android:layout_height="wrap_content"/> - - <LinearLayout - android:id="@+id/recent_tiles" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="10dp" - android:orientation="vertical" - android:background="@drawable/rounded_bg_full_large_radius" - android:clipToOutline="true"> - </LinearLayout> - </LinearLayout> - </LinearLayout> - </androidx.core.widget.NestedScrollView> -</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/people_space_tile_view.xml b/packages/SystemUI/res/layout/people_space_tile_view.xml deleted file mode 100644 index b0599caae6df..000000000000 --- a/packages/SystemUI/res/layout/people_space_tile_view.xml +++ /dev/null @@ -1,60 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - ~ Copyright (C) 2020 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. - --> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:id="@+id/tile_view" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> - - <LinearLayout - android:orientation="vertical" - android:background="?androidprv:attr/colorSurface" - android:padding="12dp" - android:elevation="4dp" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - - <LinearLayout - android:orientation="horizontal" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="start"> - - <ImageView - android:id="@+id/tile_view_person_icon" - android:layout_width="@dimen/avatar_size_for_medium" - android:layout_height="@dimen/avatar_size_for_medium" /> - - <LinearLayout - android:orientation="horizontal" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical"> - - <TextView - android:id="@+id/tile_view_name" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" - android:paddingHorizontal="16dp" - android:textSize="22sp" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical"/> - </LinearLayout> - </LinearLayout> - </LinearLayout> -</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml index e3c5a7d03d2e..5f77f61d805b 100644 --- a/packages/SystemUI/res/layout/qs_panel.xml +++ b/packages/SystemUI/res/layout/qs_panel.xml @@ -47,13 +47,12 @@ <include layout="@layout/quick_status_bar_expanded_header" /> - <include - layout="@layout/footer_actions" + <androidx.compose.ui.platform.ComposeView android:id="@+id/qs_footer_actions" android:layout_height="@dimen/footer_actions_height" android:layout_width="match_parent" android:layout_gravity="bottom" - /> + android:elevation="@dimen/qs_panel_elevation" /> <include android:id="@+id/qs_customize" diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml index 56ebc0668097..aea79e84f7e8 100644 --- a/packages/SystemUI/res/values-land/dimens.xml +++ b/packages/SystemUI/res/values-land/dimens.xml @@ -28,9 +28,7 @@ <!-- In landscape the security footer is actually part of the header, and needs to be as short as the header --> - <dimen name="qs_security_footer_single_line_height">@*android:dimen/quick_qs_offset_height</dimen> <dimen name="qs_footer_padding">14dp</dimen> - <dimen name="qs_security_footer_background_inset">12dp</dimen> <dimen name="volume_tool_tip_top_margin">12dp</dimen> <dimen name="volume_row_slider_height">128dp</dimen> diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index 664fbeb29c8e..29e0dbea24f2 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -68,11 +68,6 @@ <dimen name="qs_brightness_margin_bottom">16dp</dimen> - <!-- For large screens the security footer appears below the footer, - same as phones in portrait --> - <dimen name="qs_security_footer_single_line_height">48dp</dimen> - <dimen name="qs_security_footer_background_inset">0dp</dimen> - <dimen name="qs_panel_padding_top">8dp</dimen> <!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) --> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 380a79e5a289..8ce20684d892 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -713,7 +713,6 @@ <dimen name="qs_header_mobile_icon_size">@dimen/status_bar_icon_drawing_size</dimen> <dimen name="qs_header_carrier_separator_width">6dp</dimen> <dimen name="qs_carrier_margin_width">4dp</dimen> - <dimen name="qs_footer_icon_size">20dp</dimen> <dimen name="qs_header_height">120dp</dimen> <dimen name="qs_header_row_min_height">48dp</dimen> @@ -721,11 +720,7 @@ <dimen name="new_qs_header_non_clickable_element_height">24sp</dimen> <dimen name="qs_footer_padding">20dp</dimen> - <dimen name="qs_security_footer_height">88dp</dimen> - <dimen name="qs_security_footer_single_line_height">48dp</dimen> <dimen name="qs_footers_margin_bottom">8dp</dimen> - <dimen name="qs_security_footer_background_inset">0dp</dimen> - <dimen name="qs_security_footer_corner_radius">28dp</dimen> <dimen name="segmented_button_spacing">0dp</dimen> <dimen name="borderless_button_radius">2dp</dimen> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index b993a5ad75b9..177ba598add7 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -259,9 +259,6 @@ <!-- ID of the Scene Container root Composable view --> <item type='id' name="scene_container_root_composable" /> - <!-- Tag set on the Compose implementation of the QS footer actions. --> - <item type="id" name="tag_compose_qs_footer_actions" /> - <!-- Ids for the device entry icon. device_entry_icon_view: parent view of both device_entry_icon and device_entry_icon_bg diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index b8f71c10dc89..64717fcc8c5d 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -149,11 +149,6 @@ <item name="android:letterSpacing">0.01</item> </style> - <style name="TextAppearance.QS.SecurityFooter" parent="@style/TextAppearance.QS.Status"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> - <item name="android:textColor">?attr/onSurface</item> - </style> - <style name="TextAppearance.QS.Status.Carriers" /> <style name="TextAppearance.QS.Status.Carriers.NoCarrierText"> diff --git a/packages/SystemUI/src/com/android/systemui/brightness/dagger/ScreenBrightnessModule.kt b/packages/SystemUI/src/com/android/systemui/brightness/dagger/ScreenBrightnessModule.kt index 2b9fc73458d8..7a9429e56c88 100644 --- a/packages/SystemUI/src/com/android/systemui/brightness/dagger/ScreenBrightnessModule.kt +++ b/packages/SystemUI/src/com/android/systemui/brightness/dagger/ScreenBrightnessModule.kt @@ -20,8 +20,15 @@ import com.android.systemui.brightness.data.repository.BrightnessPolicyRepositor import com.android.systemui.brightness.data.repository.BrightnessPolicyRepositoryImpl import com.android.systemui.brightness.data.repository.ScreenBrightnessDisplayManagerRepository import com.android.systemui.brightness.data.repository.ScreenBrightnessRepository +import com.android.systemui.brightness.shared.model.BrightnessLog +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogBufferFactory +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.TableLogBufferFactory import dagger.Binds import dagger.Module +import dagger.Provides @Module interface ScreenBrightnessModule { @@ -33,4 +40,20 @@ interface ScreenBrightnessModule { @Binds fun bindPolicyRepository(impl: BrightnessPolicyRepositoryImpl): BrightnessPolicyRepository + + companion object { + @Provides + @SysUISingleton + @BrightnessLog + fun providesBrightnessTableLog(factory: TableLogBufferFactory): TableLogBuffer { + return factory.create("BrightnessTableLog", 50) + } + + @Provides + @SysUISingleton + @BrightnessLog + fun providesBrightnessLog(factory: LogBufferFactory): LogBuffer { + return factory.create("BrightnessLog", 50) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/brightness/data/model/LinearBrightness.kt b/packages/SystemUI/src/com/android/systemui/brightness/data/model/LinearBrightness.kt deleted file mode 100644 index 608f301da85d..000000000000 --- a/packages/SystemUI/src/com/android/systemui/brightness/data/model/LinearBrightness.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.brightness.data.model - -@JvmInline -value class LinearBrightness(val floatValue: Float) { - fun clamp(min: LinearBrightness, max: LinearBrightness): LinearBrightness { - return if (floatValue < min.floatValue) { - min - } else if (floatValue > max.floatValue) { - max - } else { - this - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt index 9ed11d13d4d4..37d1887730b9 100644 --- a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt @@ -19,12 +19,18 @@ package com.android.systemui.brightness.data.repository import android.annotation.SuppressLint import android.hardware.display.BrightnessInfo import android.hardware.display.DisplayManager -import com.android.systemui.brightness.data.model.LinearBrightness +import com.android.systemui.brightness.shared.model.BrightnessLog +import com.android.systemui.brightness.shared.model.LinearBrightness +import com.android.systemui.brightness.shared.model.formatBrightness +import com.android.systemui.brightness.shared.model.logDiffForTable import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.DisplayId +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.table.TableLogBuffer import javax.inject.Inject import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope @@ -32,13 +38,13 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -78,6 +84,8 @@ class ScreenBrightnessDisplayManagerRepository constructor( @DisplayId private val displayId: Int, private val displayManager: DisplayManager, + @BrightnessLog private val logBuffer: LogBuffer, + @BrightnessLog private val tableBuffer: TableLogBuffer, @Application private val applicationScope: CoroutineScope, @Background private val backgroundContext: CoroutineContext, ) : ScreenBrightnessRepository { @@ -100,6 +108,7 @@ constructor( displayManager.setBrightness(displayId, value) } } + logBrightnessChange(call is SetBrightnessMethod.Permanent, value) } } } @@ -147,13 +156,15 @@ constructor( brightnessInfo .filterNotNull() .map { LinearBrightness(it.brightnessMinimum) } - .shareIn(applicationScope, SharingStarted.WhileSubscribed()) + .logDiffForTable(tableBuffer, TABLE_PREFIX_LINEAR, TABLE_COLUMN_MIN, null) + .stateIn(applicationScope, SharingStarted.WhileSubscribed(), LinearBrightness(0f)) - override val maxLinearBrightness = + override val maxLinearBrightness: SharedFlow<LinearBrightness> = brightnessInfo .filterNotNull() .map { LinearBrightness(it.brightnessMaximum) } - .shareIn(applicationScope, SharingStarted.WhileSubscribed()) + .logDiffForTable(tableBuffer, TABLE_PREFIX_LINEAR, TABLE_COLUMN_MAX, null) + .stateIn(applicationScope, SharingStarted.WhileSubscribed(), LinearBrightness(1f)) override suspend fun getMinMaxLinearBrightness(): Pair<LinearBrightness, LinearBrightness> { val brightnessInfo = brightnessInfo.value ?: brightnessInfoValue() @@ -166,7 +177,8 @@ constructor( brightnessInfo .filterNotNull() .map { LinearBrightness(it.brightness) } - .shareIn(applicationScope, SharingStarted.WhileSubscribed()) + .logDiffForTable(tableBuffer, TABLE_PREFIX_LINEAR, TABLE_COLUMN_BRIGHTNESS, null) + .stateIn(applicationScope, SharingStarted.WhileSubscribed(), LinearBrightness(0f)) override fun setTemporaryBrightness(value: LinearBrightness) { apiQueue.trySend(SetBrightnessMethod.Temporary(value)) @@ -183,4 +195,21 @@ constructor( @JvmInline value class Permanent(override val value: LinearBrightness) : SetBrightnessMethod } + + private fun logBrightnessChange(permanent: Boolean, value: Float) { + logBuffer.log( + LOG_BUFFER_BRIGHTNESS_CHANGE_TAG, + if (permanent) LogLevel.DEBUG else LogLevel.VERBOSE, + { str1 = value.formatBrightness() }, + { "Change requested: $str1" } + ) + } + + private companion object { + const val TABLE_COLUMN_BRIGHTNESS = "brightness" + const val TABLE_COLUMN_MIN = "min" + const val TABLE_COLUMN_MAX = "max" + const val TABLE_PREFIX_LINEAR = "linear" + const val LOG_BUFFER_BRIGHTNESS_CHANGE_TAG = "BrightnessChange" + } } diff --git a/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt b/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt index 799a0a14c99d..5647f521762f 100644 --- a/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt @@ -17,12 +17,20 @@ package com.android.systemui.brightness.domain.interactor import com.android.settingslib.display.BrightnessUtils -import com.android.systemui.brightness.data.model.LinearBrightness import com.android.systemui.brightness.data.repository.ScreenBrightnessRepository -import com.android.systemui.brightness.shared.GammaBrightness +import com.android.systemui.brightness.shared.model.BrightnessLog +import com.android.systemui.brightness.shared.model.GammaBrightness +import com.android.systemui.brightness.shared.model.LinearBrightness +import com.android.systemui.brightness.shared.model.logDiffForTable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.log.table.TableLogBuffer import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn /** * Converts between [GammaBrightness] and [LinearBrightness]. @@ -34,6 +42,8 @@ class ScreenBrightnessInteractor @Inject constructor( private val screenBrightnessRepository: ScreenBrightnessRepository, + @Application private val applicationScope: CoroutineScope, + @BrightnessLog private val tableBuffer: TableLogBuffer, ) { /** Maximum value in the Gamma space for brightness */ val maxGammaBrightness = GammaBrightness(BrightnessUtils.GAMMA_SPACE_MAX) @@ -45,15 +55,17 @@ constructor( * Brightness in the Gamma space for the current display. It will always represent a value * between [minGammaBrightness] and [maxGammaBrightness] */ - val gammaBrightness = + val gammaBrightness: Flow<GammaBrightness> = with(screenBrightnessRepository) { combine( - linearBrightness, - minLinearBrightness, - maxLinearBrightness, - ) { brightness, min, max -> - brightness.toGammaBrightness(min, max) - } + linearBrightness, + minLinearBrightness, + maxLinearBrightness, + ) { brightness, min, max -> + brightness.toGammaBrightness(min, max) + } + .logDiffForTable(tableBuffer, TABLE_PREFIX_GAMMA, TABLE_COLUMN_BRIGHTNESS, null) + .stateIn(applicationScope, SharingStarted.WhileSubscribed(), GammaBrightness(0)) } /** Sets the brightness temporarily, while the user is changing it. */ @@ -91,4 +103,9 @@ constructor( BrightnessUtils.convertLinearToGammaFloat(floatValue, min.floatValue, max.floatValue) ) } + + private companion object { + const val TABLE_COLUMN_BRIGHTNESS = "brightness" + const val TABLE_PREFIX_GAMMA = "gamma" + } } diff --git a/packages/SystemUI/src/com/android/systemui/brightness/shared/GammaBrightness.kt b/packages/SystemUI/src/com/android/systemui/brightness/shared/model/BrightnessLog.kt index e20d003bb989..b514fefbff0e 100644 --- a/packages/SystemUI/src/com/android/systemui/brightness/shared/GammaBrightness.kt +++ b/packages/SystemUI/src/com/android/systemui/brightness/shared/model/BrightnessLog.kt @@ -14,16 +14,11 @@ * limitations under the License. */ -package com.android.systemui.brightness.shared +package com.android.systemui.brightness.shared.model -import androidx.annotation.IntRange -import com.android.settingslib.display.BrightnessUtils +import javax.inject.Qualifier -@JvmInline -value class GammaBrightness( - @IntRange( - from = BrightnessUtils.GAMMA_SPACE_MIN.toLong(), - to = BrightnessUtils.GAMMA_SPACE_MAX.toLong() - ) - val value: Int -) +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class BrightnessLog() diff --git a/packages/SystemUI/src/com/android/systemui/brightness/shared/model/GammaBrightness.kt b/packages/SystemUI/src/com/android/systemui/brightness/shared/model/GammaBrightness.kt new file mode 100644 index 000000000000..7eba6268869c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/brightness/shared/model/GammaBrightness.kt @@ -0,0 +1,50 @@ +/* + * 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.brightness.shared.model + +import androidx.annotation.IntRange +import com.android.settingslib.display.BrightnessUtils +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.util.kotlin.pairwiseBy +import kotlinx.coroutines.flow.Flow + +@JvmInline +value class GammaBrightness( + @IntRange( + from = BrightnessUtils.GAMMA_SPACE_MIN.toLong(), + to = BrightnessUtils.GAMMA_SPACE_MAX.toLong() + ) + val value: Int +) + +internal fun Flow<GammaBrightness>.logDiffForTable( + tableLogBuffer: TableLogBuffer, + columnPrefix: String, + columnName: String, + initialValue: GammaBrightness?, +): Flow<GammaBrightness> { + val initialValueFun = { + tableLogBuffer.logChange(columnPrefix, columnName, initialValue?.value, isInitial = true) + initialValue + } + return this.pairwiseBy(initialValueFun) { prevVal: GammaBrightness?, newVal: GammaBrightness -> + if (prevVal != newVal) { + tableLogBuffer.logChange(columnPrefix, columnName, newVal.value) + } + newVal + } +} diff --git a/packages/SystemUI/src/com/android/systemui/brightness/shared/model/LinearBrightness.kt b/packages/SystemUI/src/com/android/systemui/brightness/shared/model/LinearBrightness.kt new file mode 100644 index 000000000000..1c886e6b1477 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/brightness/shared/model/LinearBrightness.kt @@ -0,0 +1,65 @@ +/* + * 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.brightness.shared.model + +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.util.kotlin.pairwiseBy +import kotlinx.coroutines.flow.Flow + +@JvmInline +value class LinearBrightness(val floatValue: Float) { + fun clamp(min: LinearBrightness, max: LinearBrightness): LinearBrightness { + return if (floatValue < min.floatValue) { + min + } else if (floatValue > max.floatValue) { + max + } else { + this + } + } + + val loggableString: String + get() = floatValue.formatBrightness() +} + +fun Float.formatBrightness(): String { + return "%.3f".format(this) +} + +internal fun Flow<LinearBrightness>.logDiffForTable( + tableLogBuffer: TableLogBuffer, + columnPrefix: String, + columnName: String, + initialValue: LinearBrightness?, +): Flow<LinearBrightness> { + val initialValueFun = { + tableLogBuffer.logChange( + columnPrefix, + columnName, + initialValue?.loggableString, + isInitial = true + ) + initialValue + } + return this.pairwiseBy(initialValueFun) { prevVal: LinearBrightness?, newVal: LinearBrightness + -> + if (prevVal != newVal) { + tableLogBuffer.logChange(columnPrefix, columnName, newVal.loggableString) + } + newVal + } +} diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt index a51d8ff4faa5..f991d5b8405f 100644 --- a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt +++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt @@ -33,14 +33,13 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.PlatformSlider -import com.android.systemui.brightness.shared.GammaBrightness +import com.android.systemui.brightness.shared.model.GammaBrightness import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel import com.android.systemui.brightness.ui.viewmodel.Drag import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text import com.android.systemui.common.ui.compose.Icon import com.android.systemui.utils.PolicyRestriction -import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @Composable @@ -107,8 +106,8 @@ fun BrightnessSliderContainer( viewModel: BrightnessSliderViewModel, modifier: Modifier = Modifier, ) { - val gamma: Int by - viewModel.currentBrightness.map { it.value }.collectAsStateWithLifecycle(initialValue = 0) + val state by viewModel.currentBrightness.collectAsStateWithLifecycle() + val gamma = state.value val coroutineScope = rememberCoroutineScope() val restriction by viewModel.policyRestriction.collectAsStateWithLifecycle( diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt index f0988ba96bcd..16a1dcc0aef5 100644 --- a/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt @@ -18,14 +18,18 @@ package com.android.systemui.brightness.ui.viewmodel import com.android.systemui.brightness.domain.interactor.BrightnessPolicyEnforcementInteractor import com.android.systemui.brightness.domain.interactor.ScreenBrightnessInteractor -import com.android.systemui.brightness.shared.GammaBrightness +import com.android.systemui.brightness.shared.model.GammaBrightness import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.res.R import com.android.systemui.utils.PolicyRestriction import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.stateIn @SysUISingleton class BrightnessSliderViewModel @@ -33,8 +37,14 @@ class BrightnessSliderViewModel constructor( private val screenBrightnessInteractor: ScreenBrightnessInteractor, private val brightnessPolicyEnforcementInteractor: BrightnessPolicyEnforcementInteractor, + @Application private val applicationScope: CoroutineScope, ) { - val currentBrightness = screenBrightnessInteractor.gammaBrightness + val currentBrightness = + screenBrightnessInteractor.gammaBrightness.stateIn( + applicationScope, + SharingStarted.WhileSubscribed(), + GammaBrightness(0) + ) val maxBrightness = screenBrightnessInteractor.maxGammaBrightness val minBrightness = screenBrightnessInteractor.minGammaBrightness diff --git a/packages/SystemUI/src/com/android/systemui/dock/DockManagerExtensions.kt b/packages/SystemUI/src/com/android/systemui/dock/DockManagerExtensions.kt index 4dbb32da62c2..1bbdfcd88548 100644 --- a/packages/SystemUI/src/com/android/systemui/dock/DockManagerExtensions.kt +++ b/packages/SystemUI/src/com/android/systemui/dock/DockManagerExtensions.kt @@ -19,16 +19,18 @@ package com.android.systemui.dock import com.android.systemui.common.coroutine.ConflatedCallbackFlow import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged /** - * Retrieves whether or not the device is docked according to DockManager. Emits a starting value - * of isDocked. + * Retrieves whether or not the device is docked according to DockManager. Emits a starting value of + * isDocked. */ fun DockManager.retrieveIsDocked(): Flow<Boolean> = ConflatedCallbackFlow.conflatedCallbackFlow { - val callback = DockManager.DockEventListener { trySend(isDocked) } - addListener(callback) - trySend(isDocked) + val callback = DockManager.DockEventListener { trySend(isDocked) } + addListener(callback) + trySend(isDocked) - awaitClose { removeListener(callback) } - }
\ No newline at end of file + awaitClose { removeListener(callback) } + } + .distinctUntilChanged() diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java index b0d134f5f15f..f6ac7a579140 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java @@ -33,8 +33,8 @@ import com.android.systemui.dreams.DreamOverlayNotificationCountProvider; import com.android.systemui.dreams.DreamOverlayService; import com.android.systemui.dreams.SystemDialogsCloser; import com.android.systemui.dreams.complication.dagger.ComplicationComponent; -import com.android.systemui.dreams.homecontrols.DreamActivityProvider; -import com.android.systemui.dreams.homecontrols.DreamActivityProviderImpl; +import com.android.systemui.dreams.homecontrols.DreamServiceDelegate; +import com.android.systemui.dreams.homecontrols.DreamServiceDelegateImpl; import com.android.systemui.dreams.homecontrols.HomeControlsDreamService; import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.pipeline.shared.TileSpec; @@ -202,8 +202,8 @@ public interface DreamModule { } - /** Provides activity for dream service */ + /** Provides delegate to allow for testing of dream service */ @Binds - DreamActivityProvider bindActivityProvider(DreamActivityProviderImpl impl); + DreamServiceDelegate bindDreamDelegate(DreamServiceDelegateImpl impl); } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegate.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegate.kt new file mode 100644 index 000000000000..2cfb02eadd19 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegate.kt @@ -0,0 +1,34 @@ +/* + * 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.dreams.homecontrols + +import android.app.Activity +import android.service.dreams.DreamService + +/** Provides abstraction for [DreamService] methods, so they can be mocked in tests. */ +interface DreamServiceDelegate { + /** Wrapper for [DreamService.getActivity] which can be mocked in tests. */ + fun getActivity(dreamService: DreamService): Activity? + + /** Wrapper for [DreamService.wakeUp] which can be mocked in tests. */ + fun wakeUp(dreamService: DreamService) + + /** Wrapper for [DreamService.finish] which can be mocked in tests. */ + fun finish(dreamService: DreamService) + + /** Wrapper for [DreamService.getRedirectWake] which can be mocked in tests. */ + fun redirectWake(dreamService: DreamService): Boolean +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegateImpl.kt index 0854e939645b..7dc5434c595e 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegateImpl.kt @@ -19,8 +19,20 @@ import android.app.Activity import android.service.dreams.DreamService import javax.inject.Inject -class DreamActivityProviderImpl @Inject constructor() : DreamActivityProvider { +class DreamServiceDelegateImpl @Inject constructor() : DreamServiceDelegate { override fun getActivity(dreamService: DreamService): Activity { return dreamService.activity } + + override fun finish(dreamService: DreamService) { + dreamService.finish() + } + + override fun wakeUp(dreamService: DreamService) { + dreamService.wakeUp() + } + + override fun redirectWake(dreamService: DreamService): Boolean { + return dreamService.redirectWake + } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt index 76187c614b5d..77c54ec1eac3 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt @@ -31,6 +31,7 @@ import com.android.systemui.log.dagger.DreamLog import com.android.systemui.util.wakelock.WakeLock import com.android.systemui.util.wakelock.WakeLock.Builder.NO_TIMEOUT import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -46,7 +47,7 @@ constructor( private val taskFragmentFactory: TaskFragmentComponent.Factory, private val homeControlsComponentInteractor: HomeControlsComponentInteractor, private val wakeLockBuilder: WakeLock.Builder, - private val dreamActivityProvider: DreamActivityProvider, + private val dreamServiceDelegate: DreamServiceDelegate, @Background private val bgDispatcher: CoroutineDispatcher, @DreamLog logBuffer: LogBuffer ) : DreamService() { @@ -65,7 +66,7 @@ constructor( override fun onAttachedToWindow() { super.onAttachedToWindow() - val activity = dreamActivityProvider.getActivity(this) + val activity = dreamServiceDelegate.getActivity(this) if (activity == null) { finish() return @@ -79,9 +80,9 @@ constructor( taskFragmentFactory .create( activity = activity, - onCreateCallback = this::onTaskFragmentCreated, + onCreateCallback = { launchActivity() }, onInfoChangedCallback = this::onTaskFragmentInfoChanged, - hide = { endDream() } + hide = { endDream(false) } ) .apply { createTaskFragment() } @@ -91,16 +92,24 @@ constructor( private fun onTaskFragmentInfoChanged(taskFragmentInfo: TaskFragmentInfo) { if (taskFragmentInfo.isEmpty) { logger.d("Finishing dream due to TaskFragment being empty") - endDream() + endDream(true) } } - private fun endDream() { + private fun endDream(handleRedirect: Boolean) { homeControlsComponentInteractor.onDreamEndUnexpectedly() - finish() + if (handleRedirect && dreamServiceDelegate.redirectWake(this)) { + dreamServiceDelegate.wakeUp(this) + serviceScope.launch { + delay(ACTIVITY_RESTART_DELAY) + launchActivity() + } + } else { + dreamServiceDelegate.finish(this) + } } - private fun onTaskFragmentCreated(taskFragmentInfo: TaskFragmentInfo) { + private fun launchActivity() { val setting = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value val componentName = homeControlsComponentInteractor.panelComponent.value logger.d("Starting embedding $componentName") @@ -134,6 +143,14 @@ constructor( * complete. */ val CANCELLATION_DELAY_AFTER_DETACHED = 5.seconds + + /** + * Defines the delay after wakeup where we should attempt to restart the embedded activity. + * When a wakeup is redirected, the dream service may keep running. In this case, we should + * restart the activity if it finished. This delays ensures the activity is only restarted + * after the wakeup transition has played. + */ + val ACTIVITY_RESTART_DELAY = 334.milliseconds const val TAG = "HomeControlsDreamService" } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index c08434015ab1..f4f8796ebffc 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -459,14 +459,6 @@ object Flags { @JvmField val ENABLE_CLOCK_KEYGUARD_PRESENTATION = releasedFlag("enable_clock_keyguard_presentation") - /** Enable the Compose implementation of the PeopleSpaceActivity. */ - @JvmField - val COMPOSE_PEOPLE_SPACE = releasedFlag("compose_people_space") - - /** Enable the Compose implementation of the Quick Settings footer actions. */ - @JvmField - val COMPOSE_QS_FOOTER_ACTIONS = releasedFlag("compose_qs_footer_actions") - /** Enable the share wifi button in Quick Settings internet dialog. */ @JvmField val SHARE_WIFI_QS_BUTTON = releasedFlag("share_wifi_qs_button") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt index 9b07675f672c..756c6c20e58d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt @@ -57,7 +57,7 @@ constructor( @Background private val scope: CoroutineScope, @Background bgDispatcher: CoroutineDispatcher, @Main mainDispatcher: CoroutineDispatcher, - private val keyguardInteractor: KeyguardInteractor, + keyguardInteractor: KeyguardInteractor, private val communalInteractor: CommunalInteractor, powerInteractor: PowerInteractor, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, @@ -70,6 +70,7 @@ constructor( bgDispatcher = bgDispatcher, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + keyguardInteractor = keyguardInteractor, ) { override fun start() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt index 01109af79c1d..2a9ee9fb8779 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt @@ -48,7 +48,7 @@ constructor( @Background private val scope: CoroutineScope, @Background bgDispatcher: CoroutineDispatcher, @Main mainDispatcher: CoroutineDispatcher, - private val keyguardInteractor: KeyguardInteractor, + keyguardInteractor: KeyguardInteractor, powerInteractor: PowerInteractor, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, val deviceEntryRepository: DeviceEntryRepository, @@ -60,6 +60,7 @@ constructor( bgDispatcher = bgDispatcher, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + keyguardInteractor = keyguardInteractor, ) { override fun start() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt index 7d3de306d621..f5e98f1fedfe 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt @@ -47,7 +47,7 @@ constructor( @Background private val scope: CoroutineScope, @Background bgDispatcher: CoroutineDispatcher, @Main mainDispatcher: CoroutineDispatcher, - private val keyguardInteractor: KeyguardInteractor, + keyguardInteractor: KeyguardInteractor, powerInteractor: PowerInteractor, private val communalInteractor: CommunalInteractor, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, @@ -60,6 +60,7 @@ constructor( bgDispatcher = bgDispatcher, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + keyguardInteractor = keyguardInteractor, ) { override fun start() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt index 63294f7609a2..47aa02a0be52 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt @@ -45,7 +45,7 @@ constructor( @Background private val scope: CoroutineScope, @Background bgDispatcher: CoroutineDispatcher, @Main mainDispatcher: CoroutineDispatcher, - private val keyguardInteractor: KeyguardInteractor, + keyguardInteractor: KeyguardInteractor, powerInteractor: PowerInteractor, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, ) : @@ -56,6 +56,7 @@ constructor( bgDispatcher = bgDispatcher, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + keyguardInteractor = keyguardInteractor, ) { override fun start() { 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 7961b45830d4..25c3b0d395c0 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 @@ -51,7 +51,7 @@ constructor( @Background private val scope: CoroutineScope, @Background bgDispatcher: CoroutineDispatcher, @Main mainDispatcher: CoroutineDispatcher, - private val keyguardInteractor: KeyguardInteractor, + keyguardInteractor: KeyguardInteractor, private val glanceableHubTransitions: GlanceableHubTransitions, powerInteractor: PowerInteractor, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, @@ -63,6 +63,7 @@ constructor( bgDispatcher = bgDispatcher, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + keyguardInteractor = keyguardInteractor, ) { override fun start() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt index ca6ab3ef52d8..e516fa3c44bb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt @@ -48,7 +48,7 @@ constructor( @Main mainDispatcher: CoroutineDispatcher, @Background bgDispatcher: CoroutineDispatcher, private val glanceableHubTransitions: GlanceableHubTransitions, - private val keyguardInteractor: KeyguardInteractor, + keyguardInteractor: KeyguardInteractor, override val transitionRepository: KeyguardTransitionRepository, transitionInteractor: KeyguardTransitionInteractor, powerInteractor: PowerInteractor, @@ -61,6 +61,7 @@ constructor( bgDispatcher = bgDispatcher, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + keyguardInteractor = keyguardInteractor, ) { override fun start() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt index 8ca29c80c2e9..a540d761c38f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt @@ -49,7 +49,7 @@ constructor( @Background private val scope: CoroutineScope, @Background bgDispatcher: CoroutineDispatcher, @Main mainDispatcher: CoroutineDispatcher, - private val keyguardInteractor: KeyguardInteractor, + keyguardInteractor: KeyguardInteractor, powerInteractor: PowerInteractor, private val communalInteractor: CommunalInteractor, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, @@ -64,6 +64,7 @@ constructor( bgDispatcher = bgDispatcher, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + keyguardInteractor = keyguardInteractor, ) { override fun start() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index f1e98f3bbe6d..8cab3cd35dcf 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -58,7 +58,7 @@ constructor( @Background private val scope: CoroutineScope, @Background bgDispatcher: CoroutineDispatcher, @Main mainDispatcher: CoroutineDispatcher, - private val keyguardInteractor: KeyguardInteractor, + keyguardInteractor: KeyguardInteractor, private val flags: FeatureFlags, private val shadeRepository: ShadeRepository, powerInteractor: PowerInteractor, @@ -73,6 +73,7 @@ constructor( bgDispatcher = bgDispatcher, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + keyguardInteractor = keyguardInteractor, ) { override fun start() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt index 2603aab2781b..86d4cfb916ed 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt @@ -45,7 +45,7 @@ constructor( @Background private val scope: CoroutineScope, @Background bgDispatcher: CoroutineDispatcher, @Main mainDispatcher: CoroutineDispatcher, - private val keyguardInteractor: KeyguardInteractor, + keyguardInteractor: KeyguardInteractor, powerInteractor: PowerInteractor, private val communalInteractor: CommunalInteractor, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, @@ -57,6 +57,7 @@ constructor( bgDispatcher = bgDispatcher, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + keyguardInteractor = keyguardInteractor, ) { override fun start() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt index 76a822369b0c..19b2b81c4b27 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt @@ -52,7 +52,7 @@ constructor( @Background private val scope: CoroutineScope, @Background bgDispatcher: CoroutineDispatcher, @Main mainDispatcher: CoroutineDispatcher, - private val keyguardInteractor: KeyguardInteractor, + keyguardInteractor: KeyguardInteractor, private val communalInteractor: CommunalInteractor, private val flags: FeatureFlags, private val keyguardSecurityModel: KeyguardSecurityModel, @@ -67,6 +67,7 @@ constructor( bgDispatcher = bgDispatcher, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + keyguardInteractor = keyguardInteractor, ) { override fun start() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt index ccce3bf1397c..8ffa4bb8e4fb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt @@ -105,33 +105,35 @@ constructor( } return combine( - quickAffordanceAlwaysVisible(position), - keyguardInteractor.isDozing, - if (SceneContainerFlag.isEnabled) { - sceneInteractor - .get() - .transitionState - .map { - when (it) { - is ObservableTransitionState.Idle -> - it.currentScene == Scenes.Lockscreen - is ObservableTransitionState.Transition -> - it.fromScene == Scenes.Lockscreen || it.toScene == Scenes.Lockscreen + quickAffordanceAlwaysVisible(position), + keyguardInteractor.isDozing, + if (SceneContainerFlag.isEnabled) { + sceneInteractor + .get() + .transitionState + .map { + when (it) { + is ObservableTransitionState.Idle -> + it.currentScene == Scenes.Lockscreen + is ObservableTransitionState.Transition -> + it.fromScene == Scenes.Lockscreen || + it.toScene == Scenes.Lockscreen + } } - } - .distinctUntilChanged() - } else { - keyguardInteractor.isKeyguardShowing - }, - shadeInteractor.anyExpansion.map { it < 1.0f }.distinctUntilChanged(), - biometricSettingsRepository.isCurrentUserInLockdown, - ) { affordance, isDozing, isKeyguardShowing, isQuickSettingsVisible, isUserInLockdown -> - if (!isDozing && isKeyguardShowing && isQuickSettingsVisible && !isUserInLockdown) { - affordance - } else { - KeyguardQuickAffordanceModel.Hidden + .distinctUntilChanged() + } else { + keyguardInteractor.isKeyguardShowing + }, + shadeInteractor.anyExpansion.map { it < 1.0f }.distinctUntilChanged(), + biometricSettingsRepository.isCurrentUserInLockdown, + ) { affordance, isDozing, isKeyguardShowing, isQuickSettingsVisible, isUserInLockdown -> + if (!isDozing && isKeyguardShowing && isQuickSettingsVisible && !isUserInLockdown) { + affordance + } else { + KeyguardQuickAffordanceModel.Hidden + } } - } + .distinctUntilChanged() } /** 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 323ceef06a97..e14820714c9b 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 @@ -53,6 +53,7 @@ sealed class TransitionInteractor( val bgDispatcher: CoroutineDispatcher, val powerInteractor: PowerInteractor, val keyguardOcclusionInteractor: KeyguardOcclusionInteractor, + val keyguardInteractor: KeyguardInteractor, ) { val name = this::class.simpleName ?: "UnknownTransitionInteractor" abstract val transitionRepository: KeyguardTransitionRepository @@ -164,14 +165,10 @@ sealed class TransitionInteractor( @Deprecated("Will be merged into maybeStartTransitionToOccludedOrInsecureCamera") suspend fun maybeHandleInsecurePowerGesture(): Boolean { if (keyguardOcclusionInteractor.shouldTransitionFromPowerButtonGesture()) { - if (transitionInteractor.getCurrentState() == KeyguardState.GONE) { - // If the current state is GONE when the launch gesture is triggered, it means we - // were in transition from GONE -> DOZING/AOD due to the first power button tap. The - // second tap indicates that the user's intent was actually to launch the unlocked - // (insecure) camera, so we should transition back to GONE. + if (keyguardInteractor.isKeyguardDismissible.value) { startTransitionTo( KeyguardState.GONE, - ownerReason = "Power button gesture while GONE" + ownerReason = "Power button gesture while keyguard is dismissible" ) return true diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt index 23c2491813f7..807c322cc566 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt @@ -65,7 +65,7 @@ object KeyguardIndicationAreaBinder { val configurationBasedDimensions = MutableStateFlow(loadFromResources(view)) val disposableHandle = view.repeatWhenAttached { - repeatOnLifecycle(Lifecycle.State.STARTED) { + repeatOnLifecycle(Lifecycle.State.CREATED) { launch("$TAG#viewModel.alpha") { // Do not independently apply alpha, as [KeyguardRootViewModel] should work // for this and all its children diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt index b9a79dccf76b..1cf009d13e9b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt @@ -30,6 +30,7 @@ import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle +import com.android.app.tracing.coroutines.launch import com.android.settingslib.Utils import com.android.systemui.animation.Expandable import com.android.systemui.animation.view.LaunchableImageView @@ -80,8 +81,8 @@ object KeyguardQuickAffordanceViewBinder { val configurationBasedDimensions = MutableStateFlow(loadFromResources(view)) val disposableHandle = view.repeatWhenAttached { - repeatOnLifecycle(Lifecycle.State.STARTED) { - launch { + repeatOnLifecycle(Lifecycle.State.CREATED) { + launch("$TAG#viewModel.collect") { viewModel.collect { buttonModel -> updateButton( view = button, @@ -93,7 +94,7 @@ object KeyguardQuickAffordanceViewBinder { } } - launch { + launch("$TAG#updateButtonAlpha") { updateButtonAlpha( view = button, viewModel = viewModel, @@ -101,7 +102,7 @@ object KeyguardQuickAffordanceViewBinder { ) } - launch { + launch("$TAG#configurationBasedDimensions") { configurationBasedDimensions.collect { dimensions -> button.updateLayoutParams<ViewGroup.LayoutParams> { width = dimensions.buttonSizePx.width @@ -323,4 +324,6 @@ object KeyguardQuickAffordanceViewBinder { private data class ConfigurationBasedDimensions( val buttonSizePx: Size, ) + + private const val TAG = "KeyguardQuickAffordanceViewBinder" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt index 8409f15dca81..448a71c36a99 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt @@ -23,9 +23,12 @@ import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.BurnInModel +import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.res.R import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged @@ -42,6 +45,7 @@ constructor( private val burnInInteractor: BurnInInteractor, private val shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel, configurationInteractor: ConfigurationInteractor, + keyguardTransitionInteractor: KeyguardTransitionInteractor, ) { /** Notifies when a new configuration is set */ @@ -69,12 +73,22 @@ constructor( .distinctUntilChanged() } + @OptIn(ExperimentalCoroutinesApi::class) private val burnIn: Flow<BurnInModel> = - burnInInteractor - .burnIn( - xDimenResourceId = R.dimen.burn_in_prevention_offset_x, - yDimenResourceId = R.dimen.default_burn_in_prevention_offset, - ) + combine( + burnInInteractor.burnIn( + xDimenResourceId = R.dimen.burn_in_prevention_offset_x, + yDimenResourceId = R.dimen.default_burn_in_prevention_offset, + ), + keyguardTransitionInteractor.transitionValue(KeyguardState.AOD), + ) { burnIn, aodTransitionValue -> + BurnInModel( + (burnIn.translationX * aodTransitionValue).toInt(), + (burnIn.translationY * aodTransitionValue).toInt(), + burnIn.scale, + burnIn.scaleClockOnly, + ) + } .distinctUntilChanged() /** An observable for the x-offset by which the indication area should be translated. */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt index c4383fc0857d..244d842b7073 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.annotation.VisibleForTesting +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor @@ -28,19 +29,23 @@ import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAfforda import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.stateIn @OptIn(ExperimentalCoroutinesApi::class) class KeyguardQuickAffordancesCombinedViewModel @Inject constructor( + @Application applicationScope: CoroutineScope, private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor, private val keyguardInteractor: KeyguardInteractor, shadeInteractor: ShadeInteractor, @@ -84,15 +89,20 @@ constructor( /** The only time the expansion is important is while lockscreen is actively displayed */ private val shadeExpansionAlpha = combine( - showingLockscreen, - shadeInteractor.anyExpansion, - ) { showingLockscreen, expansion -> - if (showingLockscreen) { - 1 - expansion - } else { - 0f + showingLockscreen, + shadeInteractor.anyExpansion, + ) { showingLockscreen, expansion -> + if (showingLockscreen) { + 1 - expansion + } else { + 0f + } } - } + .stateIn( + scope = applicationScope, + started = SharingStarted.Lazily, + initialValue = 0f, + ) /** * ID of the slot that's currently selected in the preview that renders exclusively in the diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt index deb0fed0ffc8..954e94af1c1a 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt @@ -26,11 +26,7 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.android.compose.theme.PlatformTheme -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.people.ui.compose.PeopleScreen -import com.android.systemui.people.ui.view.PeopleViewBinder -import com.android.systemui.people.ui.view.PeopleViewBinder.bind import com.android.systemui.people.ui.viewmodel.PeopleViewModel import javax.inject.Inject import kotlinx.coroutines.launch @@ -38,10 +34,7 @@ import kotlinx.coroutines.launch /** People Tile Widget configuration activity that shows the user their conversation tiles. */ class PeopleSpaceActivity @Inject -constructor( - private val viewModelFactory: PeopleViewModel.Factory, - private val featureFlags: FeatureFlags, -) : ComponentActivity() { +constructor(private val viewModelFactory: PeopleViewModel.Factory) : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setResult(RESULT_CANCELED) @@ -66,17 +59,7 @@ constructor( } // Set the content of the activity, using either the View or Compose implementation. - if (featureFlags.isEnabled(Flags.COMPOSE_PEOPLE_SPACE)) { - Log.d(TAG, "Using the Compose implementation of the PeopleSpaceActivity") - setContent { - PlatformTheme { PeopleScreen(viewModel, onResult = { finishActivity(it) }) } - } - } else { - Log.d(TAG, "Using the View implementation of the PeopleSpaceActivity") - val view = PeopleViewBinder.create(this) - bind(view, viewModel, lifecycleOwner = this, onResult = { finishActivity(it) }) - setContentView(view) - } + setContent { PlatformTheme { PeopleScreen(viewModel, onResult = { finishActivity(it) }) } } } private fun finishActivity(result: PeopleViewModel.Result) { diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java deleted file mode 100644 index 59c76adb721b..000000000000 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2020 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.people; - -import android.app.people.PeopleSpaceTile; -import android.content.Context; -import android.content.pm.LauncherApps; -import android.graphics.Bitmap; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.systemui.res.R; - -/** - * PeopleSpaceTileView renders an individual person's tile with associated status. - */ -public class PeopleSpaceTileView extends LinearLayout { - - private View mTileView; - private TextView mNameView; - private ImageView mPersonIconView; - - public PeopleSpaceTileView(Context context, ViewGroup view, String shortcutId, boolean isLast) { - super(context); - mTileView = view.findViewWithTag(shortcutId); - if (mTileView == null) { - LayoutInflater inflater = LayoutInflater.from(context); - mTileView = inflater.inflate(R.layout.people_space_tile_view, view, false); - view.addView(mTileView, LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT); - mTileView.setTag(shortcutId); - - // If it's not the last conversation in this section, add a divider. - if (!isLast) { - inflater.inflate(R.layout.people_space_activity_list_divider, view, true); - } - } - mNameView = mTileView.findViewById(R.id.tile_view_name); - mPersonIconView = mTileView.findViewById(R.id.tile_view_person_icon); - } - - /** Sets the name text on the tile. */ - public void setName(String name) { - mNameView.setText(name); - } - - /** Sets the person and package drawable on the tile. */ - public void setPersonIcon(Bitmap bitmap) { - mPersonIconView.setImageBitmap(bitmap); - } - - /** Sets the click listener of the tile. */ - public void setOnClickListener(LauncherApps launcherApps, PeopleSpaceTile tile) { - mTileView.setOnClickListener(v -> - launcherApps.startShortcut(tile.getPackageName(), tile.getId(), null, null, - tile.getUserHandle())); - } - - /** Sets the click listener of the tile directly. */ - public void setOnClickListener(OnClickListener onClickListener) { - mTileView.setOnClickListener(onClickListener); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt deleted file mode 100644 index 10a2b3ce7b85..000000000000 --- a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright (C) 2022 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.people.ui.view - -import android.content.Context -import android.graphics.Color -import android.graphics.Outline -import android.graphics.drawable.GradientDrawable -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.ViewOutlineProvider -import android.widget.LinearLayout -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.Lifecycle.State.CREATED -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.people.PeopleSpaceTileView -import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel -import com.android.systemui.people.ui.viewmodel.PeopleViewModel -import com.android.systemui.res.R -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.launch - -/** A ViewBinder for [PeopleViewModel]. */ -object PeopleViewBinder { - private const val TAG = "PeopleViewBinder" - - /** - * The [ViewOutlineProvider] used to clip the corner radius of the recent and priority lists. - */ - private val ViewOutlineProvider = - object : ViewOutlineProvider() { - override fun getOutline(view: View, outline: Outline) { - outline.setRoundRect( - 0, - 0, - view.width, - view.height, - view.context.resources.getDimension(R.dimen.people_space_widget_radius), - ) - } - } - - /** Create a [View] that can later be [bound][bind] to a [PeopleViewModel]. */ - @JvmStatic - fun create(context: Context): ViewGroup { - return LayoutInflater.from(context) - .inflate(R.layout.people_space_activity, /* root= */ null) as ViewGroup - } - - /** Bind [view] to [viewModel]. */ - @JvmStatic - fun bind( - view: ViewGroup, - viewModel: PeopleViewModel, - lifecycleOwner: LifecycleOwner, - onResult: (PeopleViewModel.Result) -> Unit, - ) { - // Call [onResult] as soon as a result is available. - lifecycleOwner.lifecycleScope.launch { - lifecycleOwner.repeatOnLifecycle(CREATED) { - viewModel.result.collect { result -> - if (result != null) { - viewModel.clearResult() - onResult(result) - } - } - } - } - - // Start collecting the UI data once the Activity is STARTED. - lifecycleOwner.lifecycleScope.launch { - lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { - combine( - viewModel.priorityTiles, - viewModel.recentTiles, - ) { priority, recent -> - priority to recent - } - .collect { (priorityTiles, recentTiles) -> - if (priorityTiles.isNotEmpty() || recentTiles.isNotEmpty()) { - setConversationsContent( - view, - priorityTiles, - recentTiles, - viewModel.onTileClicked, - ) - } else { - setNoConversationsContent(view, viewModel.onUserJourneyCancelled) - } - } - } - } - } - - private fun setNoConversationsContent(view: ViewGroup, onGotItClicked: () -> Unit) { - // This should never happen. - if (view.childCount > 1) { - error("view has ${view.childCount} children, it should have maximum 1") - } - - // The static content for no conversations is already shown. - if (view.findViewById<View>(R.id.top_level_no_conversations) != null) { - return - } - - // If we were showing the content with conversations earlier, remove it. - if (view.childCount == 1) { - view.removeViewAt(0) - } - - val context = view.context - val noConversationsView = - LayoutInflater.from(context) - .inflate(R.layout.people_space_activity_no_conversations, /* root= */ view) - - noConversationsView.requireViewById<View>(R.id.got_it_button).setOnClickListener { - onGotItClicked() - } - - // The Tile preview has colorBackground as its background. Change it so it's different than - // the activity's background. - val item = noConversationsView.requireViewById<LinearLayout>(android.R.id.background) - val shape = item.background as GradientDrawable - val ta = - context.theme.obtainStyledAttributes( - intArrayOf(com.android.internal.R.attr.colorSurface) - ) - shape.setColor(ta.getColor(0, Color.WHITE)) - ta.recycle() - } - - private fun setConversationsContent( - view: ViewGroup, - priorityTiles: List<PeopleTileViewModel>, - recentTiles: List<PeopleTileViewModel>, - onTileClicked: (PeopleTileViewModel) -> Unit, - ) { - // This should never happen. - if (view.childCount > 1) { - error("view has ${view.childCount} children, it should have maximum 1") - } - - // Inflate the content with conversations, if it's not already. - if (view.findViewById<View>(R.id.top_level_with_conversations) == null) { - // If we were showing the content without conversations earlier, remove it. - if (view.childCount == 1) { - view.removeViewAt(0) - } - - LayoutInflater.from(view.context) - .inflate(R.layout.people_space_activity_with_conversations, /* root= */ view) - } - - // TODO(b/193782241): Replace the NestedScrollView + 2x LinearLayout from this layout into a - // single RecyclerView once this screen is tested by screenshot tests. Introduce a - // PeopleSpaceTileViewBinder that will properly create and bind the View associated to a - // PeopleSpaceTileViewModel (and remove the PeopleSpaceTileView class). - val conversationsView = view.requireViewById<View>(R.id.top_level_with_conversations) - setTileViews( - conversationsView, - R.id.priority, - R.id.priority_tiles, - priorityTiles, - onTileClicked, - ) - - setTileViews( - conversationsView, - R.id.recent, - R.id.recent_tiles, - recentTiles, - onTileClicked, - ) - } - - /** Sets a [PeopleSpaceTileView]s for each conversation. */ - private fun setTileViews( - root: View, - tilesListId: Int, - tilesId: Int, - tiles: List<PeopleTileViewModel>, - onTileClicked: (PeopleTileViewModel) -> Unit, - ) { - // Remove any previously added tile. - // TODO(b/193782241): Once this list is a big RecyclerView, set the current list and use - // DiffUtil to do as less addView/removeView as possible. - val layout = root.requireViewById<ViewGroup>(tilesId) - layout.removeAllViews() - layout.outlineProvider = ViewOutlineProvider - - val tilesListView = root.requireViewById<LinearLayout>(tilesListId) - if (tiles.isEmpty()) { - tilesListView.visibility = View.GONE - return - } - tilesListView.visibility = View.VISIBLE - - // Add each tile. - tiles.forEachIndexed { i, tile -> - val tileView = - PeopleSpaceTileView(root.context, layout, tile.key.shortcutId, i == tiles.size - 1) - bindTileView(tileView, tile, onTileClicked) - } - } - - /** Sets [tileView] with the data in [conversation]. */ - private fun bindTileView( - tileView: PeopleSpaceTileView, - tile: PeopleTileViewModel, - onTileClicked: (PeopleTileViewModel) -> Unit, - ) { - try { - tileView.setName(tile.username) - tileView.setPersonIcon(tile.icon) - tileView.setOnClickListener { onTileClicked(tile) } - } catch (e: Exception) { - Log.e(TAG, "Couldn't retrieve shortcut information", e) - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 4ee2db796aef..cc0901fca822 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -307,7 +307,7 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { } else { // Set the horizontal paddings unless the view is the Compose implementation of the // footer actions. - if (view.getTag(R.id.tag_compose_qs_footer_actions) == null) { + if (view.getId() != R.id.qs_footer_actions) { view.setPaddingRelative( mContentHorizontalPadding, view.getPaddingTop(), diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java index 1f4838e85e79..fb980d9a2d9b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java @@ -34,11 +34,11 @@ import android.util.IndentingPrintWriter; import android.util.Log; import android.view.View; import android.view.ViewGroup; -import android.widget.LinearLayout; import androidx.annotation.FloatRange; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import androidx.compose.ui.platform.ComposeView; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleRegistry; @@ -48,15 +48,12 @@ import com.android.keyguard.BouncerPanelExpansionCalculator; import com.android.systemui.Dumpable; import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.media.controls.ui.view.MediaHost; import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.qs.QSContainerController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.dagger.QSComponent; -import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder; import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.res.R; @@ -117,11 +114,9 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl private final MediaHost mQqsMediaHost; private final QSDisableFlagsLogger mQsDisableFlagsLogger; private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator; - private final FeatureFlags mFeatureFlags; private final QSLogger mLogger; private final FooterActionsController mFooterActionsController; private final FooterActionsViewModel.Factory mFooterActionsViewModelFactory; - private final FooterActionsViewBinder mFooterActionsViewBinder; private final ListeningAndVisibilityLifecycleOwner mListeningAndVisibilityLifecycleOwner; private boolean mShowCollapsedOnKeyguard; private boolean mLastKeyguardAndExpanded; @@ -172,7 +167,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl private View mRootView; @Nullable - private View mFooterActionsView; + private ComposeView mFooterActionsView; @Inject public QSImpl(RemoteInputQuickSettingsDisabler remoteInputQsDisabler, @@ -184,23 +179,19 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl DumpManager dumpManager, QSLogger qsLogger, FooterActionsController footerActionsController, FooterActionsViewModel.Factory footerActionsViewModelFactory, - FooterActionsViewBinder footerActionsViewBinder, - LargeScreenShadeInterpolator largeScreenShadeInterpolator, - FeatureFlags featureFlags) { + LargeScreenShadeInterpolator largeScreenShadeInterpolator) { mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler; mQsMediaHost = qsMediaHost; mQqsMediaHost = qqsMediaHost; mQsDisableFlagsLogger = qsDisableFlagsLogger; mLogger = qsLogger; mLargeScreenShadeInterpolator = largeScreenShadeInterpolator; - mFeatureFlags = featureFlags; mCommandQueue = commandQueue; mBypassController = keyguardBypassController; mStatusBarStateController = statusBarStateController; mDumpManager = dumpManager; mFooterActionsController = footerActionsController; mFooterActionsViewModelFactory = footerActionsViewModelFactory; - mFooterActionsViewBinder = footerActionsViewBinder; mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner(); if (SceneContainerFlag.isEnabled()) { mStatusBarState = StatusBarState.SHADE; @@ -294,43 +285,9 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl } private void bindFooterActionsView(View root) { - LinearLayout footerActionsView = root.findViewById(R.id.qs_footer_actions); - - if (!mFeatureFlags.isEnabled(Flags.COMPOSE_QS_FOOTER_ACTIONS)) { - Log.d(TAG, "Binding the View implementation of the QS footer actions"); - mFooterActionsView = footerActionsView; - mFooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel, - mListeningAndVisibilityLifecycleOwner); - return; - } - - // Compose is available, so let's use the Compose implementation of the footer actions. - Log.d(TAG, "Binding the Compose implementation of the QS footer actions"); - View composeView = QSUtils.createFooterActionsView(root.getContext(), + mFooterActionsView = root.findViewById(R.id.qs_footer_actions); + QSUtils.setFooterActionsViewContent(mFooterActionsView, mQSFooterActionsViewModel, mListeningAndVisibilityLifecycleOwner); - mFooterActionsView = composeView; - - // The id R.id.qs_footer_actions is used by QSContainerImpl to set the horizontal margin - // to all views except for qs_footer_actions, so we set it to the Compose view. - composeView.setId(R.id.qs_footer_actions); - - // Set this tag so that QSContainerImpl does not add horizontal paddings to this Compose - // implementation of the footer actions. They will be set in Compose instead so that the - // background fills the full screen width. - composeView.setTag(R.id.tag_compose_qs_footer_actions, true); - - // Set the same elevation as the View implementation, otherwise the footer actions will be - // drawn below the scroll view with QS grid and clicks won't get through on small devices - // where there isn't enough vertical space to show all the tiles and the footer actions. - composeView.setElevation( - composeView.getContext().getResources().getDimension(R.dimen.qs_panel_elevation)); - - // Replace the View by the Compose provided one. - ViewGroup parent = (ViewGroup) footerActionsView.getParent(); - ViewGroup.LayoutParams layoutParams = footerActionsView.getLayoutParams(); - int index = parent.indexOfChild(footerActionsView); - parent.removeViewAt(index); - parent.addView(composeView, index, layoutParams); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt b/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt index 15c3f271469d..5482e6da9a57 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt @@ -1,10 +1,9 @@ package com.android.systemui.qs import android.content.Context -import android.view.View +import androidx.compose.ui.platform.ComposeView import androidx.lifecycle.LifecycleOwner import com.android.compose.theme.PlatformTheme -import com.android.compose.ui.platform.DensityAwareComposeView import com.android.internal.policy.SystemBarUtils import com.android.systemui.qs.footer.ui.compose.FooterActions import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel @@ -29,13 +28,11 @@ object QSUtils { } @JvmStatic - fun createFooterActionsView( - context: Context, + fun setFooterActionsViewContent( + view: ComposeView, viewModel: FooterActionsViewModel, qsVisibilityLifecycleOwner: LifecycleOwner, - ): View { - return DensityAwareComposeView(context).apply { - setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } } - } + ) { + view.setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt deleted file mode 100644 index 0995dd4e592e..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Copyright (C) 2022 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.qs.footer.ui.binder - -import android.content.Context -import android.graphics.PorterDuff -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ImageView -import android.widget.LinearLayout -import android.widget.TextView -import androidx.core.view.isVisible -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.animation.Expandable -import com.android.systemui.common.ui.binder.IconViewBinder -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.people.ui.view.PeopleViewBinder.bind -import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel -import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel -import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel -import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel -import com.android.systemui.res.R -import javax.inject.Inject -import kotlin.math.roundToInt -import kotlinx.coroutines.launch - -/** A ViewBinder for [FooterActionsViewBinder]. */ -@SysUISingleton -class FooterActionsViewBinder @Inject constructor() { - /** Create a view that can later be [bound][bind] to a [FooterActionsViewModel]. */ - fun create(context: Context): LinearLayout { - return LayoutInflater.from(context).inflate(R.layout.footer_actions, /* root= */ null) - as LinearLayout - } - - /** Bind [view] to [viewModel]. */ - fun bind( - view: LinearLayout, - viewModel: FooterActionsViewModel, - qsVisibilityLifecycleOwner: LifecycleOwner, - ) { - view.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES - - // Add the views used by this new implementation. - val context = view.context - val inflater = LayoutInflater.from(context) - - val securityHolder = TextButtonViewHolder.createAndAdd(inflater, view) - val foregroundServicesWithTextHolder = TextButtonViewHolder.createAndAdd(inflater, view) - val foregroundServicesWithNumberHolder = NumberButtonViewHolder.createAndAdd(inflater, view) - val userSwitcherHolder = IconButtonViewHolder.createAndAdd(inflater, view, isLast = false) - val settingsHolder = - IconButtonViewHolder.createAndAdd(inflater, view, isLast = viewModel.power == null) - - // Bind the static power and settings buttons. - bindButton(settingsHolder, viewModel.settings) - - if (viewModel.power != null) { - val powerHolder = IconButtonViewHolder.createAndAdd(inflater, view, isLast = true) - bindButton(powerHolder, viewModel.power) - } - - // There are 2 lifecycle scopes we are using here: - // 1) The scope created by [repeatWhenAttached] when [view] is attached, and destroyed - // when the [view] is detached. We use this as the parent scope for all our [viewModel] - // state collection, given that we don't want to do any work when [view] is detached. - // 2) The scope owned by [lifecycleOwner], which should be RESUMED only when Quick - // Settings are visible. We use this to make sure we collect UI state only when the - // View is visible. - // - // Given that we start our collection when the Quick Settings become visible, which happens - // every time the user swipes down the shade, we remember our previous UI state already - // bound to the UI to avoid binding the same values over and over for nothing. - - // TODO(b/242040009): Look into using only a single scope. - - var previousSecurity: FooterActionsSecurityButtonViewModel? = null - var previousForegroundServices: FooterActionsForegroundServicesButtonViewModel? = null - var previousUserSwitcher: FooterActionsButtonViewModel? = null - - // Listen for ViewModel updates when the View is attached. - view.repeatWhenAttached { - val attachedScope = this.lifecycleScope - - attachedScope.launch { - // Listen for dialog requests as soon as we are attached, even when not visible. - // TODO(b/242040009): Should this move somewhere else? - launch { viewModel.observeDeviceMonitoringDialogRequests(view.context) } - - // Make sure we set the correct alphas even when QS are not currently shown. - launch { viewModel.alpha.collect { view.alpha = it } } - launch { - viewModel.backgroundAlpha.collect { - view.background?.alpha = (it * 255).roundToInt() - } - } - } - - // Listen for model changes only when QS are visible. - qsVisibilityLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { - // Security. - launch { - viewModel.security.collect { security -> - if (previousSecurity != security) { - bindSecurity(view.context, securityHolder, security) - previousSecurity = security - } - } - } - - // Foreground services. - launch { - viewModel.foregroundServices.collect { foregroundServices -> - if (previousForegroundServices != foregroundServices) { - bindForegroundService( - foregroundServicesWithNumberHolder, - foregroundServicesWithTextHolder, - foregroundServices, - ) - previousForegroundServices = foregroundServices - } - } - } - - // User switcher. - launch { - viewModel.userSwitcher.collect { userSwitcher -> - if (previousUserSwitcher != userSwitcher) { - bindButton(userSwitcherHolder, userSwitcher) - previousUserSwitcher = userSwitcher - } - } - } - } - } - } - - private fun bindSecurity( - quickSettingsContext: Context, - securityHolder: TextButtonViewHolder, - security: FooterActionsSecurityButtonViewModel?, - ) { - val securityView = securityHolder.view - securityView.isVisible = security != null - if (security == null) { - return - } - - // Make sure that the chevron is visible and that the button is clickable if there is a - // listener. - val chevron = securityHolder.chevron - val onClick = security.onClick - if (onClick != null) { - securityView.isClickable = true - securityView.setOnClickListener { - onClick(quickSettingsContext, Expandable.fromView(securityView)) - } - chevron.isVisible = true - } else { - securityView.isClickable = false - securityView.setOnClickListener(null) - chevron.isVisible = false - } - - securityHolder.text.text = security.text - securityHolder.newDot.isVisible = false - IconViewBinder.bind(security.icon, securityHolder.icon) - } - - private fun bindForegroundService( - foregroundServicesWithNumberHolder: NumberButtonViewHolder, - foregroundServicesWithTextHolder: TextButtonViewHolder, - foregroundServices: FooterActionsForegroundServicesButtonViewModel?, - ) { - val foregroundServicesWithNumberView = foregroundServicesWithNumberHolder.view - val foregroundServicesWithTextView = foregroundServicesWithTextHolder.view - if (foregroundServices == null) { - foregroundServicesWithNumberView.isVisible = false - foregroundServicesWithTextView.isVisible = false - return - } - - val foregroundServicesCount = foregroundServices.foregroundServicesCount - if (foregroundServices.displayText) { - // Button with text, icon and chevron. - foregroundServicesWithNumberView.isVisible = false - - foregroundServicesWithTextView.isVisible = true - foregroundServicesWithTextView.setOnClickListener { - foregroundServices.onClick(Expandable.fromView(foregroundServicesWithTextView)) - } - foregroundServicesWithTextHolder.text.text = foregroundServices.text - foregroundServicesWithTextHolder.newDot.isVisible = foregroundServices.hasNewChanges - } else { - // Small button with the number only. - foregroundServicesWithTextView.isVisible = false - - foregroundServicesWithNumberView.isVisible = true - foregroundServicesWithNumberView.setOnClickListener { - foregroundServices.onClick(Expandable.fromView(foregroundServicesWithNumberView)) - } - foregroundServicesWithNumberHolder.number.text = foregroundServicesCount.toString() - foregroundServicesWithNumberHolder.number.contentDescription = foregroundServices.text - foregroundServicesWithNumberHolder.newDot.isVisible = foregroundServices.hasNewChanges - } - } - - private fun bindButton(button: IconButtonViewHolder, model: FooterActionsButtonViewModel?) { - val buttonView = button.view - buttonView.id = model?.id ?: View.NO_ID - buttonView.isVisible = model != null - if (model == null) { - return - } - - val backgroundResource = - when (model.backgroundColor) { - R.attr.shadeInactive -> R.drawable.qs_footer_action_circle - R.attr.shadeActive -> R.drawable.qs_footer_action_circle_color - else -> error("Unsupported icon background resource ${model.backgroundColor}") - } - buttonView.setBackgroundResource(backgroundResource) - buttonView.setOnClickListener { model.onClick(Expandable.fromView(buttonView)) } - - val icon = model.icon - val iconView = button.icon - - IconViewBinder.bind(icon, iconView) - if (model.iconTint != null) { - iconView.setColorFilter(model.iconTint, PorterDuff.Mode.SRC_IN) - } else { - iconView.clearColorFilter() - } - } -} - -private class TextButtonViewHolder(val view: View) { - val icon = view.requireViewById<ImageView>(R.id.icon) - val text = view.requireViewById<TextView>(R.id.text) - val newDot = view.requireViewById<ImageView>(R.id.new_dot) - val chevron = view.requireViewById<ImageView>(R.id.chevron_icon) - - companion object { - fun createAndAdd(inflater: LayoutInflater, root: ViewGroup): TextButtonViewHolder { - val view = - inflater.inflate( - R.layout.footer_actions_text_button, - /* root= */ root, - /* attachToRoot= */ false, - ) - root.addView(view) - return TextButtonViewHolder(view) - } - } -} - -private class NumberButtonViewHolder(val view: View) { - val number = view.requireViewById<TextView>(R.id.number) - val newDot = view.requireViewById<ImageView>(R.id.new_dot) - - companion object { - fun createAndAdd(inflater: LayoutInflater, root: ViewGroup): NumberButtonViewHolder { - val view = - inflater.inflate( - R.layout.footer_actions_number_button, - /* root= */ root, - /* attachToRoot= */ false, - ) - root.addView(view) - return NumberButtonViewHolder(view) - } - } -} - -private class IconButtonViewHolder(val view: View) { - val icon = view.requireViewById<ImageView>(R.id.icon) - - companion object { - fun createAndAdd( - inflater: LayoutInflater, - root: ViewGroup, - isLast: Boolean, - ): IconButtonViewHolder { - val view = - inflater.inflate( - R.layout.footer_actions_icon_button, - /* root= */ root, - /* attachToRoot= */ false, - ) - - // All buttons have a background with an inset of qs_footer_action_inset, so the last - // button must have a negative inset of -qs_footer_action_inset to compensate and be - // aligned with its parent. - val marginEnd = - if (isLast) { - -view.context.resources.getDimensionPixelSize(R.dimen.qs_footer_action_inset) - } else { - 0 - } - - val size = - view.context.resources.getDimensionPixelSize(R.dimen.qs_footer_action_button_size) - root.addView( - view, - LinearLayout.LayoutParams(size, size).apply { this.marginEnd = marginEnd }, - ) - return IconButtonViewHolder(view) - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt index e581bfceb18f..095bdf2ff5bd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt @@ -19,38 +19,26 @@ package com.android.systemui.qs.panels.data.repository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.pipeline.shared.TileSpec import javax.inject.Inject -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -/** Repository for retrieving the list of [TileSpec] to be displayed as icons. */ +/** Repository for checking if a tile should be displayed as an icon. */ interface IconTilesRepository { - val iconTilesSpecs: StateFlow<Set<TileSpec>> + fun isIconTile(spec: TileSpec): Boolean } @SysUISingleton class IconTilesRepositoryImpl @Inject constructor() : IconTilesRepository { - private val _iconTilesSpecs = - MutableStateFlow( + override fun isIconTile(spec: TileSpec): Boolean { + return !LARGE_TILES.contains(spec) + } + + companion object { + private val LARGE_TILES = setOf( - TileSpec.create("airplane"), - TileSpec.create("battery"), - TileSpec.create("cameratoggle"), - TileSpec.create("cast"), - TileSpec.create("color_correction"), - TileSpec.create("inversion"), - TileSpec.create("saver"), + TileSpec.create("internet"), + TileSpec.create("bt"), TileSpec.create("dnd"), - TileSpec.create("flashlight"), - TileSpec.create("location"), - TileSpec.create("mictoggle"), - TileSpec.create("nfc"), - TileSpec.create("night"), - TileSpec.create("rotation") + TileSpec.create("cast"), ) - ) - - /** Set of toggleable tiles that are suitable for being shown as an icon. */ - override val iconTilesSpecs: StateFlow<Set<TileSpec>> = _iconTilesSpecs.asStateFlow() + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt index ccc1c6e9135c..524ea8b70100 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt @@ -20,10 +20,9 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.panels.data.repository.IconTilesRepository import com.android.systemui.qs.pipeline.shared.TileSpec import javax.inject.Inject -import kotlinx.coroutines.flow.StateFlow /** Interactor for retrieving the list of [TileSpec] to be displayed as icons. */ @SysUISingleton -class IconTilesInteractor @Inject constructor(repo: IconTilesRepository) { - val iconTilesSpecs: StateFlow<Set<TileSpec>> = repo.iconTilesSpecs +class IconTilesInteractor @Inject constructor(private val repo: IconTilesRepository) { + fun isIconTile(spec: TileSpec): Boolean = repo.isIconTile(spec) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt index b437f645d4bc..e99c64c8c1f3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt @@ -38,14 +38,13 @@ constructor( override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> { val newTiles: MutableList<TileSpec> = mutableListOf() val row = TileRow<TileSpec>(columns = gridSizeInteractor.columns.value) - val iconTilesSet = iconTilesInteractor.iconTilesSpecs.value val tilesQueue = ArrayDeque( tiles.map { SizedTile( it, width = - if (iconTilesSet.contains(it)) { + if (iconTilesInteractor.isIconTile(it)) { 1 } else { 2 diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt index 4aeaa7d23771..2f0fe221a2b4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt @@ -52,15 +52,13 @@ constructor( tiles.forEach { it.startListening(token) } onDispose { tiles.forEach { it.stopListening(token) } } } - val iconTilesSpecs by iconTilesViewModel.iconTilesSpecs.collectAsStateWithLifecycle() val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle() TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) { items( tiles.size, span = { index -> - val iconOnly = iconTilesSpecs.contains(tiles[index].spec) - if (iconOnly) { + if (iconTilesViewModel.isIconTile(tiles[index].spec)) { GridItemSpan(1) } else { GridItemSpan(2) @@ -69,7 +67,7 @@ constructor( ) { index -> Tile( tile = tiles[index], - iconOnly = iconTilesSpecs.contains(tiles[index].spec), + iconOnly = iconTilesViewModel.isIconTile(tiles[index].spec), modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height)) ) } @@ -83,12 +81,11 @@ constructor( onAddTile: (TileSpec, Int) -> Unit, onRemoveTile: (TileSpec) -> Unit, ) { - val iconOnlySpecs by iconTilesViewModel.iconTilesSpecs.collectAsStateWithLifecycle() val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle() DefaultEditTileGrid( tiles = tiles, - iconOnlySpecs = iconOnlySpecs, + isIconOnly = iconTilesViewModel::isIconTile, columns = GridCells.Fixed(columns), modifier = modifier, onAddTile = onAddTile, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt index 708ef0dd7ff6..d60076745a78 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt @@ -38,7 +38,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource @@ -66,12 +65,11 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition tiles.forEach { it.startListening(token) } onDispose { tiles.forEach { it.stopListening(token) } } } - val iconTilesSpecs by viewModel.iconTilesSpecs.collectAsStateWithLifecycle() val columns by viewModel.columns.collectAsStateWithLifecycle() val showLabels by viewModel.showLabels.collectAsStateWithLifecycle() val largeTileHeight = tileHeight() val iconTileHeight = tileHeight(showLabels) - val (smallTiles, largeTiles) = tiles.partition { iconTilesSpecs.contains(it.spec) } + val (smallTiles, largeTiles) = tiles.partition { viewModel.isIconTile(it.spec) } TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) { // Large tiles @@ -103,7 +101,6 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition onAddTile: (TileSpec, Int) -> Unit, onRemoveTile: (TileSpec) -> Unit ) { - val iconOnlySpecs by viewModel.iconTilesSpecs.collectAsStateWithLifecycle() val columns by viewModel.columns.collectAsStateWithLifecycle() val showLabels by viewModel.showLabels.collectAsStateWithLifecycle() @@ -111,8 +108,6 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState { onAddTile(it, CurrentTilesInteractor.POSITION_AT_END) } - val isIconOnly: (TileSpec) -> Boolean = - remember(iconOnlySpecs) { { tileSpec: TileSpec -> tileSpec in iconOnlySpecs } } val largeTileHeight = tileHeight() val iconTileHeight = tileHeight(showLabels) val tilePadding = dimensionResource(R.dimen.qs_tile_margin_vertical) @@ -151,7 +146,7 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition iconTileHeight = iconTileHeight, tilePadding = tilePadding, onRemoveTile = onRemoveTile, - isIconOnly = isIconOnly, + isIconOnly = viewModel::isIconTile, columns = columns, showLabels = showLabels, ) @@ -161,7 +156,7 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition iconTileHeight = iconTileHeight, tilePadding = tilePadding, addTileToEnd = addTileToEnd, - isIconOnly = isIconOnly, + isIconOnly = viewModel::isIconTile, showLabels = showLabels, columns = columns, ) @@ -232,7 +227,7 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition val largeGridHeight = gridHeight(largeTiles.size, largeTileHeight, columns / 2, tilePadding) val smallGridHeight = gridHeight(smallTiles.size, iconTileHeight, columns, tilePadding) val largeGridHeightCustom = - gridHeight(tilesCustom.size, largeTileHeight, columns / 2, tilePadding) + gridHeight(tilesCustom.size, iconTileHeight, columns, tilePadding) // Add up the height of all three grids and add padding in between val gridHeight = @@ -257,8 +252,14 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition ) fillUpRow(nTiles = smallTiles.size, columns = columns) - // Custom tiles, all large - editTiles(tilesCustom, ClickAction.ADD, addTileToEnd, isIconOnly) + // Custom tiles, all icons + editTiles( + tilesCustom, + ClickAction.ADD, + addTileToEnd, + isIconOnly, + showLabels = showLabels + ) } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt index 70d629fa7c70..7f4e0a7047b8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt @@ -60,14 +60,13 @@ constructor( // Icon [3 | 4] // Large [6 | 8] val columns = 12 - val iconTilesSpecs by iconTilesViewModel.iconTilesSpecs.collectAsStateWithLifecycle() val stretchedTiles = remember(tiles) { val sizedTiles = tiles.map { SizedTile( it, - if (iconTilesSpecs.contains(it.spec)) { + if (iconTilesViewModel.isIconTile(it.spec)) { 3 } else { 6 @@ -81,7 +80,7 @@ constructor( items(stretchedTiles.size, span = { GridItemSpan(stretchedTiles[it].width) }) { index -> Tile( tile = stretchedTiles[index].tile, - iconOnly = iconTilesSpecs.contains(stretchedTiles[index].tile.spec), + iconOnly = iconTilesViewModel.isIconTile(stretchedTiles[index].tile.spec), modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height)) ) } @@ -95,12 +94,11 @@ constructor( onAddTile: (TileSpec, Int) -> Unit, onRemoveTile: (TileSpec) -> Unit ) { - val iconOnlySpecs by iconTilesViewModel.iconTilesSpecs.collectAsStateWithLifecycle() val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle() DefaultEditTileGrid( tiles = tiles, - iconOnlySpecs = iconOnlySpecs, + isIconOnly = iconTilesViewModel::isIconTile, columns = GridCells.Fixed(columns), modifier = modifier, onAddTile = onAddTile, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt index a6838c0c06a2..f776bf08c9e4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt @@ -165,7 +165,7 @@ fun TileLazyGrid( @Composable fun DefaultEditTileGrid( tiles: List<EditTileViewModel>, - iconOnlySpecs: Set<TileSpec>, + isIconOnly: (TileSpec) -> Boolean, columns: GridCells, modifier: Modifier, onAddTile: (TileSpec, Int) -> Unit, @@ -176,8 +176,6 @@ fun DefaultEditTileGrid( val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState { onAddTile(it, CurrentTilesInteractor.POSITION_AT_END) } - val isIconOnly: (TileSpec) -> Boolean = - remember(iconOnlySpecs) { { tileSpec: TileSpec -> tileSpec in iconOnlySpecs } } TileLazyGrid(modifier = modifier, columns = columns) { // These Text are just placeholders to see the different sections. Not final UI. diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt index 9ad00c8d3cfa..117c85c9c3ba 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt @@ -20,14 +20,13 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor import com.android.systemui.qs.pipeline.shared.TileSpec import javax.inject.Inject -import kotlinx.coroutines.flow.StateFlow interface IconTilesViewModel { - val iconTilesSpecs: StateFlow<Set<TileSpec>> + fun isIconTile(spec: TileSpec): Boolean } @SysUISingleton -class IconTilesViewModelImpl @Inject constructor(interactor: IconTilesInteractor) : +class IconTilesViewModelImpl @Inject constructor(private val interactor: IconTilesInteractor) : IconTilesViewModel { - override val iconTilesSpecs = interactor.iconTilesSpecs + override fun isIconTile(spec: TileSpec): Boolean = interactor.isIconTile(spec) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt index 214e9f097642..24b80b8b069a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt @@ -158,6 +158,9 @@ constructor( override suspend fun prependDefault( userId: Int, ) { + if (retailModeRepository.inRetailMode) { + return + } userTileRepositories.get(userId)?.prependDefault() } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt index 8ad5cb2c0a34..aca8733bff7b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt @@ -60,15 +60,21 @@ constructor( _tiles = changeEvents .scan(loadTilesFromSettingsAndParse(userId)) { current, change -> - change.apply(current).also { - if (current != it) { - if (change is RestoreTiles) { - logger.logTilesRestoredAndReconciled(current, it, userId) - } else { - logger.logProcessTileChange(change, it, userId) + change + .apply(current) + .also { + if (current != it) { + if (change is RestoreTiles) { + logger.logTilesRestoredAndReconciled(current, it, userId) + } else { + logger.logProcessTileChange(change, it, userId) + } } } - } + // Distinct preserves the order of the elements removing later + // duplicates, + // all tiles should be different + .distinct() } .flowOn(backgroundDispatcher) .stateIn(applicationScope) diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt index b7fcef4376ea..97b5e87d7167 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt @@ -43,6 +43,7 @@ import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger import com.android.systemui.qs.tiles.di.NewQSTileFactory import com.android.systemui.qs.toProto +import com.android.systemui.retail.data.repository.RetailModeRepository import com.android.systemui.settings.UserTracker import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.pairwise @@ -137,6 +138,7 @@ constructor( private val installedTilesComponentRepository: InstalledTilesComponentRepository, private val userRepository: UserRepository, private val minimumTilesRepository: MinimumTilesRepository, + private val retailModeRepository: RetailModeRepository, private val customTileStatePersister: CustomTileStatePersister, private val newQSTileFactory: Lazy<NewQSTileFactory>, private val tileFactory: QSFactory, @@ -178,6 +180,14 @@ constructor( installedTilesComponentRepository.getInstalledTilesComponents(it) } + private val minTiles: Int + get() = + if (retailModeRepository.inRetailMode) { + 1 + } else { + minimumTilesRepository.minNumberOfTiles + } + init { if (featureFlags.pipelineEnabled) { startTileCollection() @@ -273,7 +283,7 @@ constructor( newTileMap.filter { it.value is TileOrNotInstalled.NotInstalled }.keys, newUser ) - if (newResolvedTiles.size < minimumTilesRepository.minNumberOfTiles) { + if (newResolvedTiles.size < minTiles) { // We ended up with not enough tiles (some may be not installed). // Prepend the default set of tiles launch { tileSpecRepository.prependDefault(currentUser.value) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index c6dfdd5c137b..1143c304f594 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -332,6 +332,21 @@ open class QSTileViewImpl @JvmOverloads constructor( override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { super.onLayout(changed, l, t, r, b) updateHeight() + maybeUpdateLongPressEffectDimensions() + } + + private fun maybeUpdateLongPressEffectDimensions() { + if (!isLongClickable || longPressEffect == null) return + + val actualHeight = if (heightOverride != HeightOverrideable.NO_OVERRIDE) { + heightOverride + } else { + measuredHeight + } + initialLongPressProperties?.height = actualHeight.toFloat() + initialLongPressProperties?.width = measuredWidth.toFloat() + finalLongPressProperties?.height = LONG_PRESS_EFFECT_HEIGHT_SCALE * actualHeight + finalLongPressProperties?.width = LONG_PRESS_EFFECT_WIDTH_SCALE * measuredWidth } override fun onFocusChanged(gainFocus: Boolean, direction: Int, previouslyFocusedRect: Rect?) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt index 1d8b7e5b6155..bf0843b8fa4e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt @@ -20,10 +20,12 @@ import android.content.Context import android.graphics.Rect import android.os.PowerManager import android.os.SystemClock +import android.util.ArraySet import android.view.GestureDetector import android.view.MotionEvent import android.view.View import android.view.ViewGroup +import android.widget.FrameLayout import androidx.activity.OnBackPressedDispatcher import androidx.activity.OnBackPressedDispatcherOwner import androidx.activity.setViewTreeOnBackPressedDispatcherOwner @@ -35,6 +37,7 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.android.compose.theme.PlatformTheme import com.android.internal.annotations.VisibleForTesting +import com.android.systemui.Flags.glanceableHubFullscreenSwipe import com.android.systemui.ambient.touch.TouchMonitor import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent import com.android.systemui.communal.dagger.Communal @@ -52,10 +55,12 @@ import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.SceneDataSourceDelegator import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf import com.android.systemui.util.kotlin.BooleanFlowOperators.not import com.android.systemui.util.kotlin.collectFlow +import java.util.function.Consumer import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch @@ -77,11 +82,38 @@ constructor( private val communalColors: CommunalColors, private val ambientTouchComponentFactory: AmbientTouchComponent.Factory, private val communalContent: CommunalContent, - @Communal private val dataSourceDelegator: SceneDataSourceDelegator + @Communal private val dataSourceDelegator: SceneDataSourceDelegator, + private val notificationStackScrollLayoutController: NotificationStackScrollLayoutController, ) : LifecycleOwner { + + private class CommunalWrapper(context: Context) : FrameLayout(context) { + private val consumers: MutableSet<Consumer<Boolean>> = ArraySet() + + override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) { + consumers.forEach { it.accept(disallowIntercept) } + super.requestDisallowInterceptTouchEvent(disallowIntercept) + } + + fun dispatchTouchEvent( + ev: MotionEvent?, + disallowInterceptConsumer: Consumer<Boolean>? + ): Boolean { + disallowInterceptConsumer?.apply { consumers.add(this) } + + try { + return super.dispatchTouchEvent(ev) + } finally { + consumers.clear() + } + } + } + /** The container view for the hub. This will not be initialized until [initView] is called. */ private var communalContainerView: View? = null + /** Wrapper around the communal container to intercept touch events */ + private var communalContainerWrapper: CommunalWrapper? = null + /** * This lifecycle is used to control when the [touchMonitor] listens to touches. The lifecycle * should only be [Lifecycle.State.RESUMED] when the hub is showing and not covered by anything, @@ -271,9 +303,13 @@ constructor( ) collectFlow(containerView, keyguardInteractor.isDreaming, { isDreaming = it }) - communalContainerView = containerView - - return containerView + if (glanceableHubFullscreenSwipe()) { + communalContainerWrapper = CommunalWrapper(containerView.context) + communalContainerWrapper?.addView(communalContainerView) + return communalContainerWrapper!! + } else { + return containerView + } } /** @@ -306,6 +342,11 @@ constructor( lifecycleRegistry.currentState = Lifecycle.State.CREATED communalContainerView = null } + + communalContainerWrapper?.let { + (it.parent as ViewGroup).removeView(it) + communalContainerWrapper = null + } } /** @@ -319,6 +360,18 @@ constructor( */ fun onTouchEvent(ev: MotionEvent): Boolean { SceneContainerFlag.assertInLegacyMode() + + // In the case that we are handling full swipes on the lockscreen, are on the lockscreen, + // and the touch is within the horizontal notification band on the screen, do not process + // the touch. + if ( + glanceableHubFullscreenSwipe() && + !hubShowing && + !notificationStackScrollLayoutController.isBelowLastNotification(ev.x, ev.y) + ) { + return false + } + return communalContainerView?.let { handleTouchEventOnCommunalView(it, ev) } ?: false } @@ -330,12 +383,16 @@ constructor( val hubOccluded = anyBouncerShowing || shadeShowing if (isDown && !hubOccluded) { - val x = ev.rawX - val inOpeningSwipeRegion: Boolean = x >= view.width - rightEdgeSwipeRegionWidth - if (inOpeningSwipeRegion || hubShowing) { - // Steal touch events when the hub is open, or if the touch started in the opening - // gesture region. + if (glanceableHubFullscreenSwipe()) { isTrackingHubTouch = true + } else { + val x = ev.rawX + val inOpeningSwipeRegion: Boolean = x >= view.width - rightEdgeSwipeRegionWidth + if (inOpeningSwipeRegion || hubShowing) { + // Steal touch events when the hub is open, or if the touch started in the + // opening gesture region. + isTrackingHubTouch = true + } } } @@ -343,10 +400,7 @@ constructor( if (isUp || isCancel) { isTrackingHubTouch = false } - dispatchTouchEvent(view, ev) - // Return true regardless of dispatch result as some touches at the start of a gesture - // may return false from dispatchTouchEvent. - return true + return dispatchTouchEvent(view, ev) } return false @@ -356,13 +410,30 @@ constructor( * Dispatches the touch event to the communal container and sends a user activity event to reset * the screen timeout. */ - private fun dispatchTouchEvent(view: View, ev: MotionEvent) { - view.dispatchTouchEvent(ev) - powerManager.userActivity( - SystemClock.uptimeMillis(), - PowerManager.USER_ACTIVITY_EVENT_TOUCH, - 0 - ) + private fun dispatchTouchEvent(view: View, ev: MotionEvent): Boolean { + try { + var handled = false + if (glanceableHubFullscreenSwipe()) { + communalContainerWrapper?.dispatchTouchEvent(ev) { + if (it) { + handled = true + } + } + return handled || hubShowing + } else { + view.dispatchTouchEvent(ev) + // Return true regardless of dispatch result as some touches at the start of a + // gesture + // may return false from dispatchTouchEvent. + return true + } + } finally { + powerManager.userActivity( + SystemClock.uptimeMillis(), + PowerManager.USER_ACTIVITY_EVENT_TOUCH, + 0 + ) + } } override val lifecycle: Lifecycle diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java index 3cf61e211e42..8d3f7284e359 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java @@ -362,20 +362,34 @@ public class NotificationGroupingUtil { } protected boolean hasSameIcon(Object parentData, Object childData) { - Icon parentIcon = ((Notification) parentData).getSmallIcon(); - Icon childIcon = ((Notification) childData).getSmallIcon(); + Icon parentIcon = getIcon((Notification) parentData); + Icon childIcon = getIcon((Notification) childData); return parentIcon.sameAs(childIcon); } + private static Icon getIcon(Notification notification) { + if (notification.shouldUseAppIcon()) { + return notification.getAppIcon(); + } + return notification.getSmallIcon(); + } + /** * @return whether two ImageViews have the same colorFilterSet or none at all */ protected boolean hasSameColor(Object parentData, Object childData) { - int parentColor = ((Notification) parentData).color; - int childColor = ((Notification) childData).color; + int parentColor = getColor((Notification) parentData); + int childColor = getColor((Notification) childData); return parentColor == childColor; } + private static int getColor(Notification notification) { + if (notification.shouldUseAppIcon()) { + return 0; // the color filter isn't applied if using the app icon + } + return notification.color; + } + @Override public boolean isEmpty(View view) { return false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt index 319b49972bd2..16d0cc42db7f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt @@ -18,25 +18,27 @@ package com.android.systemui.statusbar.notification.icon import android.app.Notification import android.content.Context +import android.graphics.drawable.Drawable import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.contentDescForNotification import javax.inject.Inject -/** - * Testable wrapper around Context. - */ -class IconBuilder @Inject constructor( - private val context: Context -) { +/** Testable wrapper around Context. */ +class IconBuilder @Inject constructor(private val context: Context) { fun createIconView(entry: NotificationEntry): StatusBarIconView { return StatusBarIconView( - context, - "${entry.sbn.packageName}/0x${Integer.toHexString(entry.sbn.id)}", - entry.sbn) + context, + "${entry.sbn.packageName}/0x${Integer.toHexString(entry.sbn.id)}", + entry.sbn + ) } fun getIconContentDescription(n: Notification): CharSequence { return contentDescForNotification(context, n) } + + fun getAppIcon(n: Notification): Drawable { + return n.loadHeaderAppIcon(context) + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt index 271b0a86ca12..3df9374da914 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt @@ -20,6 +20,8 @@ import android.app.Notification import android.app.Notification.MessagingStyle import android.app.Person import android.content.pm.LauncherApps +import android.graphics.drawable.AdaptiveIconDrawable +import android.graphics.drawable.Drawable import android.graphics.drawable.Icon import android.os.Build import android.os.Bundle @@ -165,7 +167,7 @@ constructor( Log.wtf( TAG, "Updating using the cache is not supported when the " + - "notifications_background_conversation_icons flag is off" + "notifications_background_icons flag is off" ) } if (!usingCache || !Flags.notificationsBackgroundIcons()) { @@ -216,39 +218,85 @@ constructor( @Throws(InflationException::class) private fun getIconDescriptor(entry: NotificationEntry, redact: Boolean): StatusBarIcon { - val n = entry.sbn.notification val showPeopleAvatar = !redact && isImportantConversation(entry) + // If the descriptor is already cached, return it + getCachedIconDescriptor(entry, showPeopleAvatar)?.also { + return it + } + + val n = entry.sbn.notification + var usingMonochromeAppIcon = false + val icon: Icon? + if (showPeopleAvatar) { + icon = createPeopleAvatar(entry) + } else if (android.app.Flags.notificationsUseMonochromeAppIcon()) { + if (n.shouldUseAppIcon()) { + icon = + getMonochromeAppIcon(entry)?.also { usingMonochromeAppIcon = true } + ?: n.smallIcon + } else { + icon = n.smallIcon + } + } else { + icon = n.smallIcon + } + + if (icon == null) { + throw InflationException("No icon in notification from ${entry.sbn.packageName}") + } + + val sbi = icon.toStatusBarIcon(entry) + cacheIconDescriptor(entry, sbi, showPeopleAvatar, usingMonochromeAppIcon) + return sbi + } + + private fun getCachedIconDescriptor( + entry: NotificationEntry, + showPeopleAvatar: Boolean + ): StatusBarIcon? { val peopleAvatarDescriptor = entry.icons.peopleAvatarDescriptor + val appIconDescriptor = entry.icons.appIconDescriptor val smallIconDescriptor = entry.icons.smallIconDescriptor // If cached, return corresponding cached values - if (showPeopleAvatar && peopleAvatarDescriptor != null) { - return peopleAvatarDescriptor - } else if (!showPeopleAvatar && smallIconDescriptor != null) { - return smallIconDescriptor + return when { + showPeopleAvatar && peopleAvatarDescriptor != null -> peopleAvatarDescriptor + android.app.Flags.notificationsUseMonochromeAppIcon() && appIconDescriptor != null -> + appIconDescriptor + smallIconDescriptor != null -> smallIconDescriptor + else -> null } + } - val icon = - (if (showPeopleAvatar) { - createPeopleAvatar(entry) + private fun cacheIconDescriptor( + entry: NotificationEntry, + descriptor: StatusBarIcon, + showPeopleAvatar: Boolean, + usingMonochromeAppIcon: Boolean + ) { + if (android.app.Flags.notificationsUseAppIcon() || + android.app.Flags.notificationsUseMonochromeAppIcon() + ) { + // If either of the new icon flags is enabled, we cache the icon all the time. + if (showPeopleAvatar) { + entry.icons.peopleAvatarDescriptor = descriptor + } else if (usingMonochromeAppIcon) { + // When notificationsUseMonochromeAppIcon is enabled, we use the appIconDescriptor. + entry.icons.appIconDescriptor = descriptor } else { - n.smallIcon - }) - ?: throw InflationException("No icon in notification from " + entry.sbn.packageName) - - val sbi = icon.toStatusBarIcon(entry) - - // Cache if important conversation or app icon. - if (isImportantConversation(entry) || android.app.Flags.notificationsUseAppIcon()) { + // When notificationsUseAppIcon is enabled, the app icon overrides the small icon. + // But either way, it's a good idea to cache the descriptor. + entry.icons.smallIconDescriptor = descriptor + } + } else if (isImportantConversation(entry)) { + // Old approach: cache only if important conversation. if (showPeopleAvatar) { - entry.icons.peopleAvatarDescriptor = sbi + entry.icons.peopleAvatarDescriptor = descriptor } else { - entry.icons.smallIconDescriptor = sbi + entry.icons.smallIconDescriptor = descriptor } } - - return sbi } @Throws(InflationException::class) @@ -276,6 +324,29 @@ constructor( ) } + // TODO(b/335211019): Should we merge this with the method in GroupHelper? + private fun getMonochromeAppIcon(entry: NotificationEntry): Icon? { + // TODO(b/335211019): This should be done in the background. + var monochromeIcon: Icon? = null + try { + val appIcon: Drawable = iconBuilder.getAppIcon(entry.sbn.notification) + if (appIcon is AdaptiveIconDrawable) { + if (appIcon.monochrome != null) { + monochromeIcon = + Icon.createWithResourceAdaptiveDrawable( + /* resPackage = */ entry.sbn.packageName, + /* resId = */ appIcon.sourceDrawableResId, + /* useMonochrome = */ true, + /* inset = */ -3.0f * AdaptiveIconDrawable.getExtraInsetFraction() + ) + } + } + } catch (e: Exception) { + Log.e(TAG, "Failed to getAppIcon() in getMonochromeAppIcon()", e) + } + return monochromeIcon + } + private suspend fun getLauncherShortcutIconForPeopleAvatar(entry: NotificationEntry) = withContext(bgCoroutineContext) { var icon: Icon? = null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java index 442c0978fd77..d029ce722af9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java @@ -33,6 +33,7 @@ public final class IconPack { @Nullable private final StatusBarIconView mAodIcon; @Nullable private StatusBarIcon mSmallIconDescriptor; + @Nullable private StatusBarIcon mAppIconDescriptor; @Nullable private StatusBarIcon mPeopleAvatarDescriptor; private boolean mIsImportantConversation; @@ -111,6 +112,15 @@ public final class IconPack { mPeopleAvatarDescriptor = peopleAvatarDescriptor; } + @Nullable + StatusBarIcon getAppIconDescriptor() { + return mAppIconDescriptor; + } + + void setAppIconDescriptor(@Nullable StatusBarIcon appIconDescriptor) { + mAppIconDescriptor = appIconDescriptor; + } + boolean isImportantConversation() { return mIsImportantConversation; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/AvalancheProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/AvalancheProvider.kt index c74c396741d7..c29d700396af 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/AvalancheProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/AvalancheProvider.kt @@ -21,9 +21,9 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.util.Log +import com.android.internal.logging.UiEventLogger import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.util.time.SystemClock import javax.inject.Inject // Class to track avalanche trigger event time. @@ -33,6 +33,7 @@ class AvalancheProvider constructor( private val broadcastDispatcher: BroadcastDispatcher, private val logger: VisualInterruptionDecisionLogger, + private val uiEventLogger: UiEventLogger, ) { val TAG = "AvalancheProvider" val timeoutMs = 120000 @@ -56,6 +57,7 @@ constructor( return } Log.d(TAG, "broadcastReceiver received intent.action=" + intent.action) + uiEventLogger.log(AvalancheSuppressor.AvalancheEvent.START); startTime = System.currentTimeMillis() } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt index 938a71fd7b82..42a5bdf0f19b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt @@ -33,6 +33,8 @@ import android.os.PowerManager import android.provider.Settings import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED import android.provider.Settings.Global.HEADS_UP_OFF +import com.android.internal.logging.UiEventLogger +import com.android.internal.logging.UiEvent; import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.settings.UserTracker @@ -241,12 +243,12 @@ class AlertKeyguardVisibilitySuppressor( override fun shouldSuppress(entry: NotificationEntry) = keyguardNotificationVisibilityProvider.shouldHideNotification(entry) } - class AvalancheSuppressor( private val avalancheProvider: AvalancheProvider, private val systemClock: SystemClock, private val systemSettings: SystemSettings, private val packageManager: PackageManager, + private val uiEventLogger: UiEventLogger, ) : VisualInterruptionFilter( types = setOf(PEEK, PULSE), @@ -266,6 +268,44 @@ class AvalancheSuppressor( SUPPRESS } + enum class AvalancheEvent(private val id: Int) : UiEventLogger.UiEventEnum { + @UiEvent(doc = "An avalanche event occurred but this notification was suppressed by a " + + "non-avalanche suppressor.") + START(1802), + + @UiEvent(doc = "HUN was suppressed in avalanche.") + SUPPRESS(1803), + + @UiEvent(doc = "HUN allowed during avalanche because it is high priority.") + ALLOW_CONVERSATION_AFTER_AVALANCHE(1804), + + @UiEvent(doc = "HUN allowed during avalanche because it is a high priority conversation.") + ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME(1805), + + @UiEvent(doc = "HUN allowed during avalanche because it is a call.") + ALLOW_CALLSTYLE(1806), + + @UiEvent(doc = "HUN allowed during avalanche because it is a calendar notification.") + ALLOW_CATEGORY_REMINDER(1807), + + @UiEvent(doc = "HUN allowed during avalanche because it is a calendar notification.") + ALLOW_CATEGORY_EVENT(1808), + + @UiEvent(doc = "HUN allowed during avalanche because it has a full screen intent and " + + "the full screen intent permission is granted.") + ALLOW_FSI_WITH_PERMISSION_ON(1809), + + @UiEvent(doc = "HUN allowed during avalanche because it is colorized.") + ALLOW_COLORIZED(1810), + + @UiEvent(doc = "HUN allowed during avalanche because it is an emergency notification.") + ALLOW_EMERGENCY(1811); + + override fun getId(): Int { + return id + } + } + override fun shouldSuppress(entry: NotificationEntry): Boolean { if (!isCooldownEnabled()) { return false @@ -287,41 +327,46 @@ class AvalancheSuppressor( entry.ranking.isConversation && entry.sbn.notification.getWhen() > avalancheProvider.startTime ) { + uiEventLogger.log(AvalancheEvent.ALLOW_CONVERSATION_AFTER_AVALANCHE) return State.ALLOW_CONVERSATION_AFTER_AVALANCHE } if (entry.channel?.isImportantConversation == true) { + uiEventLogger.log(AvalancheEvent.ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME) return State.ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME } if (entry.sbn.notification.isStyle(Notification.CallStyle::class.java)) { + uiEventLogger.log(AvalancheEvent.ALLOW_CALLSTYLE) return State.ALLOW_CALLSTYLE } if (entry.sbn.notification.category == CATEGORY_REMINDER) { + uiEventLogger.log(AvalancheEvent.ALLOW_CATEGORY_REMINDER) return State.ALLOW_CATEGORY_REMINDER } if (entry.sbn.notification.category == CATEGORY_EVENT) { + uiEventLogger.log(AvalancheEvent.ALLOW_CATEGORY_EVENT) return State.ALLOW_CATEGORY_EVENT } if (entry.sbn.notification.fullScreenIntent != null) { + uiEventLogger.log(AvalancheEvent.ALLOW_FSI_WITH_PERMISSION_ON) return State.ALLOW_FSI_WITH_PERMISSION_ON } - - if (entry.sbn.notification.isColorized) { - return State.ALLOW_COLORIZED - } if (entry.sbn.notification.isColorized) { + uiEventLogger.log(AvalancheEvent.ALLOW_COLORIZED) return State.ALLOW_COLORIZED } if ( packageManager.checkPermission(RECEIVE_EMERGENCY_BROADCAST, entry.sbn.packageName) == PERMISSION_GRANTED ) { + uiEventLogger.log(AvalancheEvent.ALLOW_EMERGENCY) return State.ALLOW_EMERGENCY } + uiEventLogger.log(AvalancheEvent.SUPPRESS) return State.SUPPRESS } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt index 7e16cd5a693f..84f8662f5fee 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt @@ -178,7 +178,8 @@ constructor( if (NotificationAvalancheSuppression.isEnabled) { addFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) avalancheProvider.register() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java index bd7f766c6860..d1fabb168d90 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java @@ -191,8 +191,12 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple updateTransformedTypes(); addRemainingTransformTypes(); updateCropToPaddingForImageViews(); - Notification notification = row.getEntry().getSbn().getNotification(); - mIcon.setTag(ImageTransformState.ICON_TAG, notification.getSmallIcon()); + Notification n = row.getEntry().getSbn().getNotification(); + if (n.shouldUseAppIcon()) { + mIcon.setTag(ImageTransformState.ICON_TAG, n.getAppIcon()); + } else { + mIcon.setTag(ImageTransformState.ICON_TAG, n.getSmallIcon()); + } // We need to reset all views that are no longer transforming in case a view was previously // transformed, but now we decided to transform its container instead. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt index d6c73a9dda9e..2dccea668916 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt @@ -16,24 +16,22 @@ package com.android.systemui.statusbar.notification.shared -import com.android.systemui.Flags import com.android.systemui.flags.FlagToken -import com.android.systemui.flags.RefactorFlagUtils /** Helper for reading or using the heads-up cycling flag state. */ @Suppress("NOTHING_TO_INLINE") object NotificationHeadsUpCycling { - /** The aconfig flag name - enable this feature when FLAG_NOTIFICATION_THROTTLE_HUN is on. */ - const val FLAG_NAME = Flags.FLAG_NOTIFICATION_THROTTLE_HUN + /** The aconfig flag name */ + const val FLAG_NAME = NotificationThrottleHun.FLAG_NAME /** A token used for dependency declaration */ val token: FlagToken - get() = FlagToken(FLAG_NAME, isEnabled) + get() = NotificationThrottleHun.token /** Is the heads-up cycling animation enabled */ @JvmStatic inline val isEnabled - get() = Flags.notificationThrottleHun() + get() = NotificationThrottleHun.isEnabled /** Whether to animate the bottom line when transiting from a tall HUN to a short HUN */ @JvmStatic @@ -46,13 +44,12 @@ object NotificationHeadsUpCycling { * build to ensure that the refactor author catches issues in testing. */ @JvmStatic - inline fun isUnexpectedlyInLegacyMode() = - RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + inline fun isUnexpectedlyInLegacyMode() = NotificationThrottleHun.isUnexpectedlyInLegacyMode() /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if * the flag is enabled to ensure that the refactor author catches issues in testing. */ @JvmStatic - inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) + inline fun assertInLegacyMode() = NotificationThrottleHun.assertInLegacyMode() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationThrottleHun.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationThrottleHun.kt index dd81d42b58ee..71f0de08ece3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationThrottleHun.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationThrottleHun.kt @@ -24,7 +24,7 @@ import com.android.systemui.flags.RefactorFlagUtils @Suppress("NOTHING_TO_INLINE") object NotificationThrottleHun { /** The aconfig flag name */ - const val FLAG_NAME = Flags.FLAG_NOTIFICATION_THROTTLE_HUN + const val FLAG_NAME = Flags.FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN /** A token used for dependency declaration */ val token: FlagToken @@ -33,7 +33,7 @@ object NotificationThrottleHun { /** Is the refactor enabled */ @JvmStatic inline val isEnabled - get() = Flags.notificationThrottleHun() + get() = Flags.notificationAvalancheThrottleHun() /** * Called to ensure code is only run when the flag is enabled. This protects users from the diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt index eb09e6ef39cb..a97298527e11 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt @@ -17,6 +17,8 @@ package com.android.systemui.statusbar.policy import android.util.Log import androidx.annotation.VisibleForTesting +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager @@ -35,6 +37,7 @@ class AvalancheController @Inject constructor( dumpManager: DumpManager, + private val uiEventLogger: UiEventLogger ) : Dumpable { private val tag = "AvalancheController" @@ -65,6 +68,21 @@ constructor( // For debugging only @VisibleForTesting var debugDropSet: MutableSet<HeadsUpEntry> = HashSet() + enum class ThrottleEvent(private val id: Int) : UiEventLogger.UiEventEnum { + @UiEvent(doc = "HUN was shown.") + SHOWN(1812), + + @UiEvent(doc = "HUN was dropped to show higher priority HUNs.") + DROPPED(1813), + + @UiEvent(doc = "HUN was removed while waiting to show.") + REMOVED(1814); + + override fun getId(): Int { + return id + } + } + init { dumpManager.registerNormalDumpable(tag, /* module */ this) } @@ -145,6 +163,7 @@ constructor( log { "$fn => remove from next" } if (entry in nextMap) nextMap.remove(entry) if (entry in nextList) nextList.remove(entry) + uiEventLogger.log(ThrottleEvent.REMOVED) } else if (entry in debugDropSet) { log { "$fn => remove from dropset" } debugDropSet.remove(entry) @@ -268,6 +287,7 @@ constructor( private fun showNow(entry: HeadsUpEntry, runnableList: MutableList<Runnable>) { log { "SHOW: " + getKey(entry) } + uiEventLogger.log(ThrottleEvent.SHOWN) headsUpEntryShowing = entry runnableList.forEach { @@ -295,6 +315,12 @@ constructor( // Remove runnable labels for dropped huns val listToDrop = nextList.subList(1, nextList.size) + + // Log dropped HUNs + for (e in listToDrop) { + uiEventLogger.log(ThrottleEvent.DROPPED) + } + if (debug) { // Clear runnable labels for (e in listToDrop) { diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java deleted file mode 100644 index 2cad8442e3ba..000000000000 --- a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright (C) 2019 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.util.concurrency; - -import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; - -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Process; - -import com.android.systemui.Flags; -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Background; -import com.android.systemui.dagger.qualifiers.BroadcastRunning; -import com.android.systemui.dagger.qualifiers.LongRunning; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.dagger.qualifiers.NotifInflation; - -import dagger.Module; -import dagger.Provides; - -import java.util.concurrent.Executor; - -import javax.inject.Named; - -/** - * Dagger Module for classes found within the concurrent package. - */ -@Module -public abstract class SysUIConcurrencyModule { - - // Slow BG executor can potentially affect UI if UI is waiting for an updated state from this - // thread - private static final Long BG_SLOW_DISPATCH_THRESHOLD = 1000L; - private static final Long BG_SLOW_DELIVERY_THRESHOLD = 1000L; - private static final Long LONG_SLOW_DISPATCH_THRESHOLD = 2500L; - private static final Long LONG_SLOW_DELIVERY_THRESHOLD = 2500L; - private static final Long BROADCAST_SLOW_DISPATCH_THRESHOLD = 1000L; - private static final Long BROADCAST_SLOW_DELIVERY_THRESHOLD = 1000L; - private static final Long NOTIFICATION_INFLATION_SLOW_DISPATCH_THRESHOLD = 1000L; - private static final Long NOTIFICATION_INFLATION_SLOW_DELIVERY_THRESHOLD = 1000L; - - /** Background Looper */ - @Provides - @SysUISingleton - @Background - public static Looper provideBgLooper() { - HandlerThread thread = new HandlerThread("SysUiBg", - Process.THREAD_PRIORITY_BACKGROUND); - thread.start(); - thread.getLooper().setSlowLogThresholdMs(BG_SLOW_DISPATCH_THRESHOLD, - BG_SLOW_DELIVERY_THRESHOLD); - return thread.getLooper(); - } - - /** BroadcastRunning Looper (for sending and receiving broadcasts) */ - @Provides - @SysUISingleton - @BroadcastRunning - public static Looper provideBroadcastRunningLooper() { - HandlerThread thread = new HandlerThread("BroadcastRunning", - Process.THREAD_PRIORITY_BACKGROUND); - thread.start(); - thread.getLooper().setSlowLogThresholdMs(BROADCAST_SLOW_DISPATCH_THRESHOLD, - BROADCAST_SLOW_DELIVERY_THRESHOLD); - return thread.getLooper(); - } - - /** Long running tasks Looper */ - @Provides - @SysUISingleton - @LongRunning - public static Looper provideLongRunningLooper() { - HandlerThread thread = new HandlerThread("SysUiLng", - Process.THREAD_PRIORITY_BACKGROUND); - thread.start(); - thread.getLooper().setSlowLogThresholdMs(LONG_SLOW_DISPATCH_THRESHOLD, - LONG_SLOW_DELIVERY_THRESHOLD); - return thread.getLooper(); - } - - /** Notification inflation Looper */ - @Provides - @SysUISingleton - @NotifInflation - public static Looper provideNotifInflationLooper(@Background Looper bgLooper) { - if (!Flags.dedicatedNotifInflationThread()) { - return bgLooper; - } - - final HandlerThread thread = new HandlerThread("NotifInflation", - Process.THREAD_PRIORITY_BACKGROUND); - thread.start(); - final Looper looper = thread.getLooper(); - looper.setSlowLogThresholdMs(NOTIFICATION_INFLATION_SLOW_DISPATCH_THRESHOLD, - NOTIFICATION_INFLATION_SLOW_DELIVERY_THRESHOLD); - return looper; - } - - /** - * Background Handler. - * - * Prefer the Background Executor when possible. - */ - @Provides - @Background - public static Handler provideBgHandler(@Background Looper bgLooper) { - return new Handler(bgLooper); - } - - /** - * Provide a BroadcastRunning Executor (for sending and receiving broadcasts). - */ - @Provides - @SysUISingleton - @BroadcastRunning - public static Executor provideBroadcastRunningExecutor(@BroadcastRunning Looper looper) { - return new ExecutorImpl(looper); - } - - /** - * Provide a Long running Executor. - */ - @Provides - @SysUISingleton - @LongRunning - public static Executor provideLongRunningExecutor(@LongRunning Looper looper) { - return new ExecutorImpl(looper); - } - - /** - * Provide a Long running Executor. - */ - @Provides - @SysUISingleton - @LongRunning - public static DelayableExecutor provideLongRunningDelayableExecutor( - @LongRunning Looper looper) { - return new ExecutorImpl(looper); - } - - /** - * Provide a Background-Thread Executor. - */ - @Provides - @SysUISingleton - @Background - public static Executor provideBackgroundExecutor(@Background Looper looper) { - return new ExecutorImpl(looper); - } - - /** - * Provide a Background-Thread Executor. - */ - @Provides - @SysUISingleton - @Background - public static DelayableExecutor provideBackgroundDelayableExecutor(@Background Looper looper) { - return new ExecutorImpl(looper); - } - - /** - * Provide a Background-Thread Executor. - */ - @Provides - @SysUISingleton - @Background - public static RepeatableExecutor provideBackgroundRepeatableExecutor( - @Background DelayableExecutor exec) { - return new RepeatableExecutorImpl(exec); - } - - /** - * Provide a Main-Thread Executor. - */ - @Provides - @SysUISingleton - @Main - public static RepeatableExecutor provideMainRepeatableExecutor(@Main DelayableExecutor exec) { - return new RepeatableExecutorImpl(exec); - } - - /** */ - @Provides - @Main - public static MessageRouter providesMainMessageRouter( - @Main DelayableExecutor executor) { - return new MessageRouterImpl(executor); - } - - /** */ - @Provides - @Background - public static MessageRouter providesBackgroundMessageRouter( - @Background DelayableExecutor executor) { - return new MessageRouterImpl(executor); - } - - /** */ - @Provides - @SysUISingleton - @Named(TIME_TICK_HANDLER_NAME) - public static Handler provideTimeTickHandler() { - HandlerThread thread = new HandlerThread("TimeTick"); - thread.start(); - return new Handler(thread.getLooper()); - } - - /** */ - @Provides - @SysUISingleton - @NotifInflation - public static Executor provideNotifInflationExecutor(@NotifInflation Looper looper) { - return new ExecutorImpl(looper); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.kt b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.kt new file mode 100644 index 000000000000..83e3428bb95f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.kt @@ -0,0 +1,194 @@ +/* + * 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.util.concurrency + +import android.os.Handler +import android.os.HandlerThread +import android.os.Looper +import android.os.Process +import com.android.systemui.Dependency +import com.android.systemui.Flags +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.BroadcastRunning +import com.android.systemui.dagger.qualifiers.LongRunning +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.dagger.qualifiers.NotifInflation +import dagger.Module +import dagger.Provides +import java.util.concurrent.Executor +import javax.inject.Named + +/** Dagger Module for classes found within the concurrent package. */ +@Module +object SysUIConcurrencyModule { + // Slow BG executor can potentially affect UI if UI is waiting for an updated state from this + // thread + private const val BG_SLOW_DISPATCH_THRESHOLD = 1000L + private const val BG_SLOW_DELIVERY_THRESHOLD = 1000L + private const val LONG_SLOW_DISPATCH_THRESHOLD = 2500L + private const val LONG_SLOW_DELIVERY_THRESHOLD = 2500L + private const val BROADCAST_SLOW_DISPATCH_THRESHOLD = 1000L + private const val BROADCAST_SLOW_DELIVERY_THRESHOLD = 1000L + private const val NOTIFICATION_INFLATION_SLOW_DISPATCH_THRESHOLD = 1000L + private const val NOTIFICATION_INFLATION_SLOW_DELIVERY_THRESHOLD = 1000L + + /** Background Looper */ + @Provides + @SysUISingleton + @Background + fun provideBgLooper(): Looper { + val thread = HandlerThread("SysUiBg", Process.THREAD_PRIORITY_BACKGROUND) + thread.start() + thread + .getLooper() + .setSlowLogThresholdMs(BG_SLOW_DISPATCH_THRESHOLD, BG_SLOW_DELIVERY_THRESHOLD) + return thread.getLooper() + } + + /** BroadcastRunning Looper (for sending and receiving broadcasts) */ + @Provides + @SysUISingleton + @BroadcastRunning + fun provideBroadcastRunningLooper(): Looper { + val thread = HandlerThread("BroadcastRunning", Process.THREAD_PRIORITY_BACKGROUND) + thread.start() + thread + .getLooper() + .setSlowLogThresholdMs( + BROADCAST_SLOW_DISPATCH_THRESHOLD, + BROADCAST_SLOW_DELIVERY_THRESHOLD + ) + return thread.getLooper() + } + + /** Long running tasks Looper */ + @Provides + @SysUISingleton + @LongRunning + fun provideLongRunningLooper(): Looper { + val thread = HandlerThread("SysUiLng", Process.THREAD_PRIORITY_BACKGROUND) + thread.start() + thread + .getLooper() + .setSlowLogThresholdMs(LONG_SLOW_DISPATCH_THRESHOLD, LONG_SLOW_DELIVERY_THRESHOLD) + return thread.getLooper() + } + + /** Notification inflation Looper */ + @Provides + @SysUISingleton + @NotifInflation + fun provideNotifInflationLooper(@Background bgLooper: Looper): Looper { + if (!Flags.dedicatedNotifInflationThread()) { + return bgLooper + } + val thread = HandlerThread("NotifInflation", Process.THREAD_PRIORITY_BACKGROUND) + thread.start() + val looper = thread.getLooper() + looper.setSlowLogThresholdMs( + NOTIFICATION_INFLATION_SLOW_DISPATCH_THRESHOLD, + NOTIFICATION_INFLATION_SLOW_DELIVERY_THRESHOLD + ) + return looper + } + + /** + * Background Handler. + * + * Prefer the Background Executor when possible. + */ + @Provides + @Background + fun provideBgHandler(@Background bgLooper: Looper): Handler = Handler(bgLooper) + + /** Provide a BroadcastRunning Executor (for sending and receiving broadcasts). */ + @Provides + @SysUISingleton + @BroadcastRunning + fun provideBroadcastRunningExecutor(@BroadcastRunning looper: Looper): Executor = + ExecutorImpl(looper) + + /** Provide a Long running Executor. */ + @Provides + @SysUISingleton + @LongRunning + fun provideLongRunningExecutor(@LongRunning looper: Looper): Executor = ExecutorImpl(looper) + + /** Provide a Long running Executor. */ + @Provides + @SysUISingleton + @LongRunning + fun provideLongRunningDelayableExecutor(@LongRunning looper: Looper): DelayableExecutor = + ExecutorImpl(looper) + + /** Provide a Background-Thread Executor. */ + @Provides + @SysUISingleton + @Background + fun provideBackgroundExecutor(@Background looper: Looper): Executor = ExecutorImpl(looper) + + /** Provide a Background-Thread Executor. */ + @Provides + @SysUISingleton + @Background + fun provideBackgroundDelayableExecutor(@Background looper: Looper): DelayableExecutor = + ExecutorImpl(looper) + + /** Provide a Background-Thread Executor. */ + @Provides + @SysUISingleton + @Background + fun provideBackgroundRepeatableExecutor( + @Background exec: DelayableExecutor + ): RepeatableExecutor = RepeatableExecutorImpl(exec) + + /** Provide a Main-Thread Executor. */ + @Provides + @SysUISingleton + @Main + fun provideMainRepeatableExecutor(@Main exec: DelayableExecutor): RepeatableExecutor = + RepeatableExecutorImpl(exec) + + /** */ + @Provides + @Main + fun providesMainMessageRouter(@Main executor: DelayableExecutor): MessageRouter = + MessageRouterImpl(executor) + + /** */ + @Provides + @Background + fun providesBackgroundMessageRouter(@Background executor: DelayableExecutor): MessageRouter = + MessageRouterImpl(executor) + + /** */ + @Provides + @SysUISingleton + @Named(Dependency.TIME_TICK_HANDLER_NAME) + fun provideTimeTickHandler(): Handler { + val thread = HandlerThread("TimeTick") + thread.start() + return Handler(thread.getLooper()) + } + + /** */ + @Provides + @SysUISingleton + @NotifInflation + fun provideNotifInflationExecutor(@NotifInflation looper: Looper): Executor = + ExecutorImpl(looper) +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt index 0dc264781070..79a4ae74b217 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt @@ -19,14 +19,13 @@ import com.android.settingslib.volume.data.repository.LocalMediaRepository import com.android.settingslib.volume.data.repository.LocalMediaRepositoryImpl import com.android.settingslib.volume.shared.AudioManagerEventsReceiver import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.media.controls.util.LocalMediaManagerFactory import javax.inject.Inject import kotlinx.coroutines.CoroutineScope interface LocalMediaRepositoryFactory { - fun create(packageName: String?): LocalMediaRepository + fun create(packageName: String?, coroutineScope: CoroutineScope): LocalMediaRepository } @SysUISingleton @@ -35,10 +34,12 @@ class LocalMediaRepositoryFactoryImpl constructor( private val eventsReceiver: AudioManagerEventsReceiver, private val localMediaManagerFactory: LocalMediaManagerFactory, - @Application private val coroutineScope: CoroutineScope, ) : LocalMediaRepositoryFactory { - override fun create(packageName: String?): LocalMediaRepository = + override fun create( + packageName: String?, + coroutineScope: CoroutineScope + ): LocalMediaRepository = LocalMediaRepositoryImpl( eventsReceiver, localMediaManagerFactory.create(packageName), diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt index 9fbd79accf80..31a89775e916 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt @@ -35,6 +35,7 @@ import javax.inject.Inject import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -46,6 +47,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.withContext /** Provides observable models about the current media session state. */ @@ -105,12 +107,9 @@ constructor( .filterData() .map { it?.packageName } .distinctUntilChanged() - .map { localMediaRepositoryFactory.create(it) } - .stateIn( - coroutineScope, - SharingStarted.Eagerly, - localMediaRepositoryFactory.create(null) - ) + .transformLatest { + coroutineScope { emit(localMediaRepositoryFactory.create(it, this)) } + } /** Currently connected [MediaDevice]. */ val currentConnectedDevice: Flow<MediaDevice?> = diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt index fd01b4864772..850162e65aa6 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt @@ -146,14 +146,18 @@ constructor( isEnabled = isEnabled, a11yStep = volumeRange.step, a11yClickDescription = - context.getString( - if (isMuted) { - R.string.volume_panel_hint_unmute - } else { - R.string.volume_panel_hint_mute - }, - label, - ), + if (isAffectedByMute) { + context.getString( + if (isMuted) { + R.string.volume_panel_hint_unmute + } else { + R.string.volume_panel_hint_mute + }, + label, + ) + } else { + null + }, a11yStateDescription = if (volume == volumeRange.first) { context.getString( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt index 8a12a90efc4c..c1e3e84f2bf4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt @@ -49,6 +49,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.quickaffordance.ActivationState import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.ActivityStarter @@ -289,6 +290,7 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { underTest = KeyguardQuickAffordancesCombinedViewModel( + applicationScope = kosmos.applicationCoroutineScope, quickAffordanceInteractor = KeyguardQuickAffordanceInteractor( keyguardInteractor = keyguardInteractor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java index 6956beab418e..a1c5a5feb197 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java @@ -39,7 +39,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; -import android.annotation.Nullable; import android.content.Context; import android.graphics.Rect; import android.os.Bundle; @@ -51,6 +50,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; +import androidx.compose.ui.platform.ComposeView; import androidx.lifecycle.Lifecycle; import androidx.test.filters.SmallTest; @@ -58,12 +58,10 @@ import com.android.keyguard.BouncerPanelExpansionCalculator; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.EnableSceneContainer; -import com.android.systemui.flags.FeatureFlagsClassic; import com.android.systemui.media.controls.ui.view.MediaHost; import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.dagger.QSComponent; import com.android.systemui.qs.external.TileServiceRequestController; -import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder; import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.res.R; @@ -112,9 +110,7 @@ public class QSImplTest extends SysuiTestCase { @Mock private QSSquishinessController mSquishinessController; @Mock private FooterActionsViewModel mFooterActionsViewModel; @Mock private FooterActionsViewModel.Factory mFooterActionsViewModelFactory; - @Mock private FooterActionsViewBinder mFooterActionsViewBinder; @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator; - @Mock private FeatureFlagsClassic mFeatureFlags; private ViewGroup mQsView; private final CommandQueue mCommandQueue = @@ -496,18 +492,13 @@ public class QSImplTest extends SysuiTestCase { @Test @EnableSceneContainer public void testSceneContainerFlagsEnabled_FooterActionsRemoved_controllerNotStarted() { - clearInvocations( - mFooterActionsViewBinder, mFooterActionsViewModel, mFooterActionsViewModelFactory); + clearInvocations(mFooterActionsViewModel, mFooterActionsViewModelFactory); QSImpl other = instantiate(); other.onComponentCreated(mQsComponent, null); assertThat((View) other.getView().findViewById(R.id.qs_footer_actions)).isNull(); - verifyZeroInteractions( - mFooterActionsViewModel, - mFooterActionsViewBinder, - mFooterActionsViewModelFactory - ); + verifyZeroInteractions(mFooterActionsViewModel, mFooterActionsViewModelFactory); } @Test @@ -553,9 +544,7 @@ public class QSImplTest extends SysuiTestCase { mock(QSLogger.class), mock(FooterActionsController.class), mFooterActionsViewModelFactory, - mFooterActionsViewBinder, - mLargeScreenShadeInterpolator, - mFeatureFlags + mLargeScreenShadeInterpolator ); } @@ -589,41 +578,20 @@ public class QSImplTest extends SysuiTestCase { customizer.setId(android.R.id.edit); mQsView.addView(customizer); - View footerActionsView = new FooterActionsViewBinder().create(mContext); + ComposeView footerActionsView = new ComposeView(mContext); footerActionsView.setId(R.id.qs_footer_actions); mQsView.addView(footerActionsView); } private void setUpInflater() { - LayoutInflater realInflater = LayoutInflater.from(mContext); - when(mLayoutInflater.cloneInContext(any(Context.class))).thenReturn(mLayoutInflater); when(mLayoutInflater.inflate(anyInt(), nullable(ViewGroup.class), anyBoolean())) - .thenAnswer((invocation) -> inflate(realInflater, (int) invocation.getArgument(0), - (ViewGroup) invocation.getArgument(1), - (boolean) invocation.getArgument(2))); + .thenAnswer((invocation) -> mQsView); when(mLayoutInflater.inflate(anyInt(), nullable(ViewGroup.class))) - .thenAnswer((invocation) -> inflate(realInflater, (int) invocation.getArgument(0), - (ViewGroup) invocation.getArgument(1))); + .thenAnswer((invocation) -> mQsView); mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, mLayoutInflater); } - private View inflate(LayoutInflater realInflater, int layoutRes, @Nullable ViewGroup root) { - return inflate(realInflater, layoutRes, root, root != null); - } - - private View inflate(LayoutInflater realInflater, int layoutRes, @Nullable ViewGroup root, - boolean attachToRoot) { - if (layoutRes == R.layout.footer_actions - || layoutRes == R.layout.footer_actions_text_button - || layoutRes == R.layout.footer_actions_number_button - || layoutRes == R.layout.footer_actions_icon_button) { - return realInflater.inflate(layoutRes, root, attachToRoot); - } - - return mQsView; - } - private void setupQsComponent() { when(mQsComponent.getQSPanelController()).thenReturn(mQSPanelController); when(mQsComponent.getQuickQSPanelController()).thenReturn(mQuickQSPanelController); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt index 2da4b7296c35..87031d93db15 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt @@ -31,9 +31,6 @@ import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -46,22 +43,21 @@ import org.junit.runner.RunWith class GridConsistencyInteractorTest : SysuiTestCase() { private val iconOnlyTiles = - MutableStateFlow( - setOf( - TileSpec.create("smallA"), - TileSpec.create("smallB"), - TileSpec.create("smallC"), - TileSpec.create("smallD"), - TileSpec.create("smallE"), - ) + setOf( + TileSpec.create("smallA"), + TileSpec.create("smallB"), + TileSpec.create("smallC"), + TileSpec.create("smallD"), + TileSpec.create("smallE"), ) private val kosmos = testKosmos().apply { iconTilesRepository = object : IconTilesRepository { - override val iconTilesSpecs: StateFlow<Set<TileSpec>> - get() = iconOnlyTiles.asStateFlow() + override fun isIconTile(spec: TileSpec): Boolean { + return iconOnlyTiles.contains(spec) + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt index bda48adbfcc3..1eb6d63c5a39 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt @@ -25,9 +25,6 @@ import com.android.systemui.qs.panels.data.repository.iconTilesRepository import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -37,21 +34,20 @@ import org.junit.runner.RunWith class InfiniteGridConsistencyInteractorTest : SysuiTestCase() { private val iconOnlyTiles = - MutableStateFlow( - setOf( - TileSpec.create("smallA"), - TileSpec.create("smallB"), - TileSpec.create("smallC"), - TileSpec.create("smallD"), - TileSpec.create("smallE"), - ) + setOf( + TileSpec.create("smallA"), + TileSpec.create("smallB"), + TileSpec.create("smallC"), + TileSpec.create("smallD"), + TileSpec.create("smallE"), ) private val kosmos = testKosmos().apply { iconTilesRepository = object : IconTilesRepository { - override val iconTilesSpecs: StateFlow<Set<TileSpec>> - get() = iconOnlyTiles.asStateFlow() + override fun isIconTile(spec: TileSpec): Boolean { + return iconOnlyTiles.contains(spec) + } } } private val underTest = with(kosmos) { infiniteGridConsistencyInteractor } 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 bde1445acfa8..b8267a0e83d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt @@ -18,6 +18,8 @@ package com.android.systemui.shade import android.graphics.Rect import android.os.PowerManager +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.ViewUtils @@ -30,6 +32,7 @@ import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.systemui.Flags +import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE import com.android.systemui.SysuiTestCase import com.android.systemui.ambient.touch.TouchHandler import com.android.systemui.ambient.touch.TouchMonitor @@ -51,6 +54,7 @@ import com.android.systemui.kosmos.testScope 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.notification.stack.notificationStackScrollLayoutController import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.google.common.truth.Truth.assertThat @@ -64,9 +68,11 @@ import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyFloat import org.mockito.Mock import org.mockito.Mockito.times import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @ExperimentalCoroutinesApi @@ -124,6 +130,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { ambientTouchComponentFactory, communalContent, kosmos.sceneDataSourceDelegator, + kosmos.notificationStackScrollLayoutController ) } testableLooper = TestableLooper.get(this) @@ -166,6 +173,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { ambientTouchComponentFactory, communalContent, kosmos.sceneDataSourceDelegator, + kosmos.notificationStackScrollLayoutController ) // First call succeeds. @@ -176,6 +184,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE) @Test fun onTouchEvent_communalClosed_doesNotIntercept() = with(kosmos) { @@ -187,6 +196,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE) @Test fun onTouchEvent_openGesture_interceptsTouches() = with(kosmos) { @@ -204,6 +214,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE) @Test fun onTouchEvent_communalTransitioning_interceptsTouches() = with(kosmos) { @@ -230,6 +241,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE) @Test fun onTouchEvent_communalOpen_interceptsTouches() = with(kosmos) { @@ -244,6 +256,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE) @Test fun onTouchEvent_communalAndBouncerShowing_doesNotIntercept() = with(kosmos) { @@ -262,6 +275,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE) @Test fun onTouchEvent_communalAndShadeShowing_doesNotIntercept() = with(kosmos) { @@ -278,6 +292,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE) @Test fun onTouchEvent_containerViewDisposed_doesNotIntercept() = with(kosmos) { @@ -310,6 +325,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { ambientTouchComponentFactory, communalContent, kosmos.sceneDataSourceDelegator, + kosmos.notificationStackScrollLayoutController, ) assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED) @@ -329,6 +345,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { ambientTouchComponentFactory, communalContent, kosmos.sceneDataSourceDelegator, + kosmos.notificationStackScrollLayoutController, ) // Only initView without attaching a view as we don't want the flows to start collecting @@ -499,13 +516,30 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @Test + @EnableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE) + fun fullScreenSwipeGesture_doNotProcessTouchesInNotificationStack() = + with(kosmos) { + testScope.runTest { + // Communal is closed. + goToScene(CommunalScenes.Blank) + `when`( + notificationStackScrollLayoutController.isBelowLastNotification( + anyFloat(), + anyFloat() + ) + ) + .thenReturn(false) + assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse() + } + } + private fun initAndAttachContainerView() { containerView = View(context) parentView = FrameLayout(context) - parentView.addView(containerView) - underTest.initView(containerView) + parentView.addView(underTest.initView(containerView)) // Attach the view so that flows start collecting. ViewUtils.attachView(parentView, CONTAINER_WIDTH, CONTAINER_HEIGHT) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt index 7903a731c1d0..e984200c305e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt @@ -91,7 +91,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro avalancheProvider.startTime = whenAgo(10) withFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) { ensurePeekState() assertShouldHeadsUp( @@ -110,7 +111,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro avalancheProvider.startTime = whenAgo(10) withFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) { ensurePeekState() assertShouldNotHeadsUp( @@ -129,7 +131,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro avalancheProvider.startTime = whenAgo(10) withFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) { ensurePeekState() assertShouldHeadsUp( @@ -146,7 +149,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro avalancheProvider.startTime = whenAgo(10) withFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) { ensurePeekState() assertShouldHeadsUp( @@ -163,7 +167,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro avalancheProvider.startTime = whenAgo(10) withFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) { ensurePeekState() assertShouldHeadsUp( @@ -180,7 +185,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro avalancheProvider.startTime = whenAgo(10) withFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) { ensurePeekState() assertShouldHeadsUp( @@ -197,7 +203,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro avalancheProvider.startTime = whenAgo(10) withFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) { assertFsiNotSuppressed() } @@ -208,7 +215,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro avalancheProvider.startTime = whenAgo(10) withFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) { ensurePeekState() assertShouldHeadsUp( @@ -232,7 +240,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro ).thenReturn(PERMISSION_GRANTED) withFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) { ensurePeekState() assertShouldHeadsUp( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt index a05b5e65ce9d..ad5242e2e036 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt @@ -19,7 +19,7 @@ package com.android.systemui.brightness.data.repository import android.hardware.display.BrightnessInfo import android.hardware.display.BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE import android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF -import com.android.systemui.brightness.data.model.LinearBrightness +import com.android.systemui.brightness.shared.model.LinearBrightness import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.map diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.kt index 22784e47d277..0e8427310895 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.kt @@ -18,6 +18,15 @@ package com.android.systemui.brightness.domain.interactor import com.android.systemui.brightness.data.repository.screenBrightnessRepository import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.util.mockito.mock val Kosmos.screenBrightnessInteractor by - Kosmos.Fixture { ScreenBrightnessInteractor(screenBrightnessRepository) } + Kosmos.Fixture { + ScreenBrightnessInteractor( + screenBrightnessRepository, + applicationCoroutineScope, + mock<TableLogBuffer>(), + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt index 604c16fd9e74..5ff44e5d33c5 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt @@ -17,6 +17,8 @@ package com.android.systemui.qs.pipeline.data.repository import com.android.systemui.kosmos.Kosmos +import com.android.systemui.retail.data.repository.FakeRetailModeRepository +import com.android.systemui.retail.data.repository.RetailModeRepository /** This fake uses 0 as the minimum number of tiles. That means that no tiles is a valid state. */ var Kosmos.fakeMinimumTilesRepository by Kosmos.Fixture { MinimumTilesFixedRepository(0) } @@ -46,3 +48,6 @@ var Kosmos.installedTilesRepository: InstalledTilesComponentRepository by val Kosmos.fakeCustomTileAddedRepository by Kosmos.Fixture { FakeCustomTileAddedRepository() } var Kosmos.customTileAddedRepository: CustomTileAddedRepository by Kosmos.Fixture { fakeCustomTileAddedRepository } + +val Kosmos.fakeRetailModeRepository by Kosmos.Fixture { FakeRetailModeRepository() } +var Kosmos.retailModeRepository: RetailModeRepository by Kosmos.Fixture { fakeRetailModeRepository } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt index b870039982f1..d97a5b2bede2 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt @@ -24,6 +24,7 @@ import com.android.systemui.qs.external.tileLifecycleManagerFactory import com.android.systemui.qs.pipeline.data.repository.customTileAddedRepository import com.android.systemui.qs.pipeline.data.repository.installedTilesRepository import com.android.systemui.qs.pipeline.data.repository.minimumTilesRepository +import com.android.systemui.qs.pipeline.data.repository.retailModeRepository import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository import com.android.systemui.qs.pipeline.shared.logging.qsLogger import com.android.systemui.qs.pipeline.shared.pipelineFlagsRepository @@ -39,6 +40,7 @@ val Kosmos.currentTilesInteractor: CurrentTilesInteractor by installedTilesRepository, userRepository, minimumTilesRepository, + retailModeRepository, customTileStatePersister, { newQSTileFactory }, qsTileFactory, diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProvider.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerKosmos.kt index b35b7f5debb3..569429f180df 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProvider.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerKosmos.kt @@ -13,15 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.systemui.dreams.homecontrols -import android.app.Activity -import android.service.dreams.DreamService +package com.android.systemui.statusbar.notification.stack -fun interface DreamActivityProvider { - /** - * Provides abstraction for getting the activity associated with a dream service, so that the - * activity can be mocked in tests. - */ - fun getActivity(dreamService: DreamService): Activity? -} +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +val Kosmos.notificationStackScrollLayoutController by + Kosmos.Fixture { mock<NotificationStackScrollLayoutController>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/FakeLocalMediaRepositoryFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/FakeLocalMediaRepositoryFactory.kt index 9c902cf57fde..680535dfa909 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/FakeLocalMediaRepositoryFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/FakeLocalMediaRepositoryFactory.kt @@ -17,6 +17,7 @@ package com.android.systemui.volume.panel.component.mediaoutput.data.repository import com.android.settingslib.volume.data.repository.LocalMediaRepository +import kotlinx.coroutines.CoroutineScope class FakeLocalMediaRepositoryFactory(private val defaultProvider: () -> LocalMediaRepository) : LocalMediaRepositoryFactory { @@ -27,6 +28,8 @@ class FakeLocalMediaRepositoryFactory(private val defaultProvider: () -> LocalMe repositories[packageName] = localMediaRepository } - override fun create(packageName: String?): LocalMediaRepository = - repositories[packageName] ?: defaultProvider() + override fun create( + packageName: String?, + coroutineScope: CoroutineScope + ): LocalMediaRepository = repositories[packageName] ?: defaultProvider() } diff --git a/packages/services/VirtualCamera/OWNERS b/packages/services/VirtualCamera/OWNERS deleted file mode 100644 index c66443fb8a14..000000000000 --- a/packages/services/VirtualCamera/OWNERS +++ /dev/null @@ -1,3 +0,0 @@ -include /services/companion/java/com/android/server/companion/virtual/OWNERS -caen@google.com -jsebechlebsky@google.com
\ No newline at end of file diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 8647750d510f..ab34dd4477fd 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -2205,12 +2205,15 @@ public class OomAdjuster { != 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0; if (roForegroundAudioControl()) { // flag check - final int fgsAudioType = FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK - | FOREGROUND_SERVICE_TYPE_CAMERA - | FOREGROUND_SERVICE_TYPE_MICROPHONE - | FOREGROUND_SERVICE_TYPE_PHONE_CALL; - capabilityFromFGS |= (psr.getForegroundServiceTypes() & fgsAudioType) != 0 - ? PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL : 0; + // TODO revisit restriction of FOREGROUND_AUDIO_CONTROL when it can be + // limited to specific FGS types + //final int fgsAudioType = FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK + // | FOREGROUND_SERVICE_TYPE_CAMERA + // | FOREGROUND_SERVICE_TYPE_MICROPHONE + // | FOREGROUND_SERVICE_TYPE_PHONE_CALL; + //capabilityFromFGS |= (psr.getForegroundServiceTypes() & fgsAudioType) != 0 + // ? PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL : 0; + capabilityFromFGS |= PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL; } final boolean enabled = state.getCachedCompatChange( diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 1dc1846fbb96..1d21ccb62b8c 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -15,6 +15,8 @@ */ package com.android.server.audio; +import static android.media.audio.Flags.scoManagedByAudio; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.compat.CompatChanges; @@ -54,6 +56,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; +import android.sysprop.BluetoothProperties; import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -74,7 +77,6 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; - /** * @hide * (non final for mocking/spying) @@ -167,6 +169,15 @@ public class AudioDeviceBroker { @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S_V2) public static final long USE_SET_COMMUNICATION_DEVICE = 243827847L; + /** Indicates if headset profile connection and SCO audio control use the new implementation + * aligned with other BT profiles. True if both the feature flag Flags.scoManagedByAudio() and + * the system property audio.sco.managed.by.audio are true. + */ + private final boolean mScoManagedByAudio; + /*package*/ boolean isScoManagedByAudio() { + return mScoManagedByAudio; + } + //------------------------------------------------------------------- /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service, @NonNull AudioSystemAdapter audioSystem) { @@ -176,7 +187,8 @@ public class AudioDeviceBroker { mDeviceInventory = new AudioDeviceInventory(this); mSystemServer = SystemServerAdapter.getDefaultAdapter(mContext); mAudioSystem = audioSystem; - + mScoManagedByAudio = scoManagedByAudio() + && BluetoothProperties.isScoManagedByAudioEnabled().orElse(false); init(); } @@ -192,7 +204,8 @@ public class AudioDeviceBroker { mDeviceInventory = mockDeviceInventory; mSystemServer = mockSystemServer; mAudioSystem = audioSystem; - + mScoManagedByAudio = scoManagedByAudio() + && BluetoothProperties.isScoManagedByAudioEnabled().orElse(false); init(); } @@ -400,24 +413,24 @@ public class AudioDeviceBroker { if (client == null) { return; } - - boolean isBtScoRequested = isBluetoothScoRequested(); - if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive())) { - if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) { - Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for uid: " - + uid); - // clean up or restore previous client selection - if (prevClientDevice != null) { - addCommunicationRouteClient(cb, uid, prevClientDevice, prevPrivileged); - } else { - removeCommunicationRouteClient(cb, true); + if (!mScoManagedByAudio) { + boolean isBtScoRequested = isBluetoothScoRequested(); + if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive())) { + if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) { + Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for uid: " + + uid); + // clean up or restore previous client selection + if (prevClientDevice != null) { + addCommunicationRouteClient(cb, uid, prevClientDevice, prevPrivileged); + } else { + removeCommunicationRouteClient(cb, true); + } + postBroadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); } - postBroadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } else if (!isBtScoRequested && wasBtScoRequested) { + mBtHelper.stopBluetoothSco(eventSource); } - } else if (!isBtScoRequested && wasBtScoRequested) { - mBtHelper.stopBluetoothSco(eventSource); } - // In BT classic for communication, the device changes from a2dp to sco device, but for // LE Audio it stays the same and we must trigger the proper stream volume alignment, if // LE Audio communication device is activated after the audio system has already switched to @@ -1685,6 +1698,8 @@ public class AudioDeviceBroker { pw.println("\n" + prefix + "mAudioModeOwner: " + mAudioModeOwner); + pw.println("\n" + prefix + "mScoManagedByAudio: " + mScoManagedByAudio); + mBtHelper.dump(pw, prefix); } @@ -1837,10 +1852,10 @@ public class AudioDeviceBroker { ? mAudioService.getBluetoothContextualVolumeStream() : AudioSystem.STREAM_DEFAULT); if (btInfo.mProfile == BluetoothProfile.LE_AUDIO - || btInfo.mProfile - == BluetoothProfile.HEARING_AID) { - onUpdateCommunicationRouteClient( - isBluetoothScoRequested(), + || btInfo.mProfile == BluetoothProfile.HEARING_AID + || (mScoManagedByAudio + && btInfo.mProfile == BluetoothProfile.HEADSET)) { + onUpdateCommunicationRouteClient(isBluetoothScoRequested(), "setBluetoothActiveDevice"); } } @@ -2511,7 +2526,7 @@ public class AudioDeviceBroker { setCommunicationRouteForClient(crc.getBinder(), crc.getUid(), crc.getDevice(), BtHelper.SCO_MODE_UNDEFINED, crc.isPrivileged(), eventSource); } else { - if (!isBluetoothScoRequested() && wasBtScoRequested) { + if (!mScoManagedByAudio && !isBluetoothScoRequested() && wasBtScoRequested) { mBtHelper.stopBluetoothSco(eventSource); } updateCommunicationRoute(eventSource); @@ -2815,4 +2830,5 @@ public class AudioDeviceBroker { void clearDeviceInventory() { mDeviceInventory.clearDeviceInventory(); } + } diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index e0790da7cd09..287c92f86f0f 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -859,6 +859,15 @@ public class AudioDeviceInventory { btInfo, streamType, codec, "onSetBtActiveDevice"); } break; + case BluetoothProfile.HEADSET: + if (mDeviceBroker.isScoManagedByAudio()) { + if (switchToUnavailable) { + mDeviceBroker.onSetBtScoActiveDevice(null); + } else if (switchToAvailable) { + mDeviceBroker.onSetBtScoActiveDevice(btInfo.mDevice); + } + } + break; default: throw new IllegalArgumentException("Invalid profile " + BluetoothProfile.getProfileName(btInfo.mProfile)); } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index cb0ad78ce51c..2a23b9ca522e 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -48,6 +48,7 @@ import static android.media.audio.Flags.automaticBtDeviceType; import static android.media.audio.Flags.featureSpatialAudioHeadtrackingLowLatency; import static android.media.audio.Flags.focusFreezeTestApi; import static android.media.audio.Flags.roForegroundAudioControl; +import static android.media.audio.Flags.scoManagedByAudio; import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration; import static android.os.Process.FIRST_APPLICATION_UID; import static android.os.Process.INVALID_UID; @@ -1503,7 +1504,9 @@ public class AudioService extends IAudioService.Stub // Register for device connection intent broadcasts. IntentFilter intentFilter = new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); - intentFilter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED); + if (!mDeviceBroker.isScoManagedByAudio()) { + intentFilter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED); + } intentFilter.addAction(Intent.ACTION_DOCK_EVENT); if (mDisplayManager == null) { intentFilter.addAction(Intent.ACTION_SCREEN_ON); @@ -4529,11 +4532,12 @@ public class AudioService extends IAudioService.Stub + focusFreezeTestApi()); pw.println("\tcom.android.media.audio.disablePrescaleAbsoluteVolume:" + disablePrescaleAbsoluteVolume()); - pw.println("\tcom.android.media.audio.setStreamVolumeOrder:" + setStreamVolumeOrder()); pw.println("\tandroid.media.audio.roForegroundAudioControl:" + roForegroundAudioControl()); + pw.println("\tandroid.media.audio.scoManagedByAudio:" + + scoManagedByAudio()); pw.println("\tcom.android.media.audio.vgsVssSyncMuteOrder:" + vgsVssSyncMuteOrder()); } @@ -7859,7 +7863,8 @@ public class AudioService extends IAudioService.Stub if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK && profile != BluetoothProfile.LE_AUDIO && profile != BluetoothProfile.LE_AUDIO_BROADCAST - && profile != BluetoothProfile.HEARING_AID) { + && profile != BluetoothProfile.HEARING_AID + && !(mDeviceBroker.isScoManagedByAudio() && profile == BluetoothProfile.HEADSET)) { throw new IllegalArgumentException("Illegal BluetoothProfile profile for device " + previousDevice + " -> " + newDevice + ". Got: " + profile); } diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index b1ea85cf3fd3..6bb3eb1c3078 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -401,50 +401,67 @@ public class BtHelper { private void onScoAudioStateChanged(int state) { boolean broadcast = false; int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR; - switch (state) { - case BluetoothHeadset.STATE_AUDIO_CONNECTED: - scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED; - if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL - && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { - mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; - } else if (mDeviceBroker.isBluetoothScoRequested()) { - // broadcast intent if the connection was initated by AudioService + if (mDeviceBroker.isScoManagedByAudio()) { + switch (state) { + case BluetoothHeadset.STATE_AUDIO_CONNECTED: + mDeviceBroker.setBluetoothScoOn(true, "BtHelper.onScoAudioStateChanged"); + scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED; broadcast = true; - } - mDeviceBroker.setBluetoothScoOn(true, "BtHelper.onScoAudioStateChanged"); - break; - case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: - mDeviceBroker.setBluetoothScoOn(false, "BtHelper.onScoAudioStateChanged"); - scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; - // There are two cases where we want to immediately reconnect audio: - // 1) If a new start request was received while disconnecting: this was - // notified by requestScoState() setting state to SCO_STATE_ACTIVATE_REQ. - // 2) If audio was connected then disconnected via Bluetooth APIs and - // we still have pending activation requests by apps: this is indicated by - // state SCO_STATE_ACTIVE_EXTERNAL and BT SCO is requested. - if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) { - if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null - && connectBluetoothScoAudioHelper(mBluetoothHeadset, - mBluetoothHeadsetDevice, mScoAudioMode)) { - mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; - scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTING; + break; + case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: + mDeviceBroker.setBluetoothScoOn(false, "BtHelper.onScoAudioStateChanged"); + scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; + broadcast = true; + break; + default: + break; + } + } else { + switch (state) { + case BluetoothHeadset.STATE_AUDIO_CONNECTED: + scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED; + if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL + && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { + mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; + } else if (mDeviceBroker.isBluetoothScoRequested()) { + // broadcast intent if the connection was initated by AudioService broadcast = true; - break; } - } - if (mScoAudioState != SCO_STATE_ACTIVE_EXTERNAL) { - broadcast = true; - } - mScoAudioState = SCO_STATE_INACTIVE; - break; - case BluetoothHeadset.STATE_AUDIO_CONNECTING: - if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL - && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { - mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; - } - break; - default: - break; + mDeviceBroker.setBluetoothScoOn(true, "BtHelper.onScoAudioStateChanged"); + break; + case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: + mDeviceBroker.setBluetoothScoOn(false, "BtHelper.onScoAudioStateChanged"); + scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; + // There are two cases where we want to immediately reconnect audio: + // 1) If a new start request was received while disconnecting: this was + // notified by requestScoState() setting state to SCO_STATE_ACTIVATE_REQ. + // 2) If audio was connected then disconnected via Bluetooth APIs and + // we still have pending activation requests by apps: this is indicated by + // state SCO_STATE_ACTIVE_EXTERNAL and BT SCO is requested. + if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) { + if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null + && connectBluetoothScoAudioHelper(mBluetoothHeadset, + mBluetoothHeadsetDevice, mScoAudioMode)) { + mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTING; + broadcast = true; + break; + } + } + if (mScoAudioState != SCO_STATE_ACTIVE_EXTERNAL) { + broadcast = true; + } + mScoAudioState = SCO_STATE_INACTIVE; + break; + case BluetoothHeadset.STATE_AUDIO_CONNECTING: + if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL + && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { + mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; + } + break; + default: + break; + } } if (broadcast) { broadcastScoConnectionState(scoAudioState); @@ -454,7 +471,6 @@ public class BtHelper { newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState); sendStickyBroadcastToAll(newIntent); } - } /** * diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig index 712dcee55b7b..92fd9cbcf14e 100644 --- a/services/core/java/com/android/server/biometrics/biometrics.aconfig +++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig @@ -14,10 +14,3 @@ flag { description: "This flag controls whether virtual HAL is used for testing instead of TestHal " bug: "294254230" } - -flag { - name: "mandatory_biometrics" - namespace: "biometrics_framework" - description: "This flag controls whether LSKF fallback is removed from biometric prompt when the phone is outside trusted locations" - bug: "322081563" -} diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 182b05a68028..44846f310348 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -168,6 +168,12 @@ final class LocalDisplayAdapter extends DisplayAdapter { } SurfaceControl.DesiredDisplayModeSpecs modeSpecs = mSurfaceControlProxy.getDesiredDisplayModeSpecs(displayToken); + if (modeSpecs == null) { + // If mode specs is null, it most probably means that display got + // unplugged very rapidly. + Slog.w(TAG, "Desired display mode specs from SurfaceFlinger are null"); + return; + } LocalDisplayDevice device = mDevices.get(physicalDisplayId); if (device == null) { // Display was added. diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java index 1c958a929546..23f947cc8452 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java @@ -367,9 +367,9 @@ final class InputMethodSubtypeSwitchingController { } protected void dump(final Printer pw, final String prefix) { - for (int i = 0; i < mUsageHistoryOfSubtypeListItemIndex.length; ++i) { - final int rank = mUsageHistoryOfSubtypeListItemIndex[i]; - final ImeSubtypeListItem item = mImeSubtypeList.get(i); + for (int rank = 0; rank < mUsageHistoryOfSubtypeListItemIndex.length; ++rank) { + final int index = mUsageHistoryOfSubtypeListItemIndex[rank]; + final ImeSubtypeListItem item = mImeSubtypeList.get(index); pw.println(prefix + "rank=" + rank + " item=" + item); } } diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java index 563f93e96331..b9e09605477a 100644 --- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java +++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java @@ -84,9 +84,16 @@ class LocaleManagerBackupHelper { * from the delegate selector. */ private static final String LOCALES_FROM_DELEGATE_PREFS = "LocalesFromDelegatePrefs.xml"; + private static final String LOCALES_STAGED_DATA_PREFS = "LocalesStagedDataPrefs.xml"; + private static final String ARCHIVED_PACKAGES_PREFS = "ArchivedPackagesPrefs.xml"; // Stage data would be deleted on reboot since it's stored in memory. So it's retained until // retention period OR next reboot, whichever happens earlier. private static final Duration STAGE_DATA_RETENTION_PERIOD = Duration.ofDays(3); + // Store the locales staged data for the specified package in the SharedPreferences. The format + // is locales s:setFromDelegate + // For example: en-US s:true + private static final String STRING_SPLIT = " s:"; + private static final String KEY_STAGED_DATA_TIME = "staged_data_time"; private final LocaleManagerService mLocaleManagerService; private final PackageManager mPackageManager; @@ -94,39 +101,34 @@ class LocaleManagerBackupHelper { private final Context mContext; private final Object mStagedDataLock = new Object(); - // Staged data map keyed by user-id to handle multi-user scenario / work profiles. We are using - // SparseArray because it is more memory-efficient than a HashMap. - private final SparseArray<StagedData> mStagedData; - // SharedPreferences to store packages whose app-locale was set by a delegate, as opposed to // the application setting the app-locale itself. private final SharedPreferences mDelegateAppLocalePackages; + // For unit tests + private final SparseArray<File> mStagedDataFiles; + private final File mArchivedPackagesFile; + private final BroadcastReceiver mUserMonitor; - // To determine whether an app is pre-archived, check for Intent.EXTRA_ARCHIVAL upon receiving - // the initial PACKAGE_ADDED broadcast. If it is indeed pre-archived, perform the data - // restoration during the second PACKAGE_ADDED broadcast, which is sent subsequently when the - // app is installed. - private final Set<String> mPkgsToRestore; LocaleManagerBackupHelper(LocaleManagerService localeManagerService, PackageManager packageManager, HandlerThread broadcastHandlerThread) { this(localeManagerService.mContext, localeManagerService, packageManager, Clock.systemUTC(), - new SparseArray<>(), broadcastHandlerThread, null); + broadcastHandlerThread, null, null, null); } - @VisibleForTesting LocaleManagerBackupHelper(Context context, - LocaleManagerService localeManagerService, - PackageManager packageManager, Clock clock, SparseArray<StagedData> stagedData, - HandlerThread broadcastHandlerThread, SharedPreferences delegateAppLocalePackages) { + @VisibleForTesting + LocaleManagerBackupHelper(Context context, LocaleManagerService localeManagerService, + PackageManager packageManager, Clock clock, HandlerThread broadcastHandlerThread, + SparseArray<File> stagedDataFiles, File archivedPackagesFile, + SharedPreferences delegateAppLocalePackages) { mContext = context; mLocaleManagerService = localeManagerService; mPackageManager = packageManager; mClock = clock; - mStagedData = stagedData; mDelegateAppLocalePackages = delegateAppLocalePackages != null ? delegateAppLocalePackages - : createPersistedInfo(); - mPkgsToRestore = new ArraySet<>(); - + : createPersistedInfo(); + mArchivedPackagesFile = archivedPackagesFile; + mStagedDataFiles = stagedDataFiles; mUserMonitor = new UserMonitor(); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_REMOVED); @@ -148,7 +150,7 @@ class LocaleManagerBackupHelper { } synchronized (mStagedDataLock) { - cleanStagedDataForOldEntriesLocked(); + cleanStagedDataForOldEntriesLocked(userId); } HashMap<String, LocalesInfo> pkgStates = new HashMap<>(); @@ -207,14 +209,11 @@ class LocaleManagerBackupHelper { return out.toByteArray(); } - private void cleanStagedDataForOldEntriesLocked() { - for (int i = 0; i < mStagedData.size(); i++) { - int userId = mStagedData.keyAt(i); - StagedData stagedData = mStagedData.get(userId); - if (stagedData.mCreationTimeMillis - < mClock.millis() - STAGE_DATA_RETENTION_PERIOD.toMillis()) { - deleteStagedDataLocked(userId); - } + private void cleanStagedDataForOldEntriesLocked(@UserIdInt int userId) { + Long created_time = getStagedDataSp(userId).getLong(KEY_STAGED_DATA_TIME, -1); + if (created_time != -1 + && created_time < mClock.millis() - STAGE_DATA_RETENTION_PERIOD.toMillis()) { + deleteStagedDataLocked(userId); } } @@ -252,20 +251,16 @@ class LocaleManagerBackupHelper { // performed simultaneously. synchronized (mStagedDataLock) { // Backups for apps which are yet to be installed. - StagedData stagedData = new StagedData(mClock.millis(), new HashMap<>()); - for (String pkgName : pkgStates.keySet()) { LocalesInfo localesInfo = pkgStates.get(pkgName); // Check if the application is already installed for the concerned user. if (isPackageInstalledForUser(pkgName, userId)) { - if (mPkgsToRestore != null) { - mPkgsToRestore.remove(pkgName); - } + removeFromArchivedPackagesInfo(userId, pkgName); // Don't apply the restore if the locales have already been set for the app. checkExistingLocalesAndApplyRestore(pkgName, localesInfo, userId); } else { // Stage the data if the app isn't installed. - stagedData.mPackageStates.put(pkgName, localesInfo); + storeStagedDataInfo(userId, pkgName, localesInfo); if (DEBUG) { Slog.d(TAG, "Add locales=" + localesInfo.mLocales + " fromDelegate=" + localesInfo.mSetFromDelegate @@ -274,8 +269,9 @@ class LocaleManagerBackupHelper { } } - if (!stagedData.mPackageStates.isEmpty()) { - mStagedData.put(userId, stagedData); + // Create the time if the data is being staged. + if (!getStagedDataSp(userId).getAll().isEmpty()) { + storeStagedDataCreatedTime(userId); } } } @@ -293,14 +289,23 @@ class LocaleManagerBackupHelper { * added on device. */ void onPackageAddedWithExtras(String packageName, int uid, Bundle extras) { - boolean archived = false; + int userId = UserHandle.getUserId(uid); if (extras != null) { - archived = extras.getBoolean(Intent.EXTRA_ARCHIVAL, false); - if (archived && mPkgsToRestore != null) { - mPkgsToRestore.add(packageName); + // To determine whether an app is pre-archived, check for Intent.EXTRA_ARCHIVAL upon + // receiving the initial PACKAGE_ADDED broadcast. If it is indeed pre-archived, perform + // the data restoration during the second PACKAGE_ADDED broadcast, which is sent + // subsequently when the app is installed. + boolean archived = extras.getBoolean(Intent.EXTRA_ARCHIVAL, false); + if (DEBUG) { + Slog.d(TAG, + "onPackageAddedWithExtras packageName: " + packageName + ", userId: " + + userId + ", archived: " + archived); + } + if (archived) { + addInArchivedPackagesInfo(userId, packageName); } } - checkStageDataAndApplyRestore(packageName, uid); + checkStageDataAndApplyRestore(packageName, userId); } /** @@ -310,9 +315,32 @@ class LocaleManagerBackupHelper { */ void onPackageUpdateFinished(String packageName, int uid) { int userId = UserHandle.getUserId(uid); - if (mPkgsToRestore != null && mPkgsToRestore.contains(packageName)) { - mPkgsToRestore.remove(packageName); - checkStageDataAndApplyRestore(packageName, uid); + if (DEBUG) { + Slog.d(TAG, + "onPackageUpdateFinished userId: " + userId + ", packageName: " + packageName); + } + String user = Integer.toString(userId); + File file = getArchivedPackagesFile(); + if (file.exists()) { + SharedPreferences sp = getArchivedPackagesSp(file); + Set<String> packageNames = new ArraySet<>(sp.getStringSet(user, new ArraySet<>())); + if (packageNames.remove(packageName)) { + SharedPreferences.Editor editor = sp.edit(); + if (packageNames.isEmpty()) { + if (!editor.remove(user).commit()) { + Slog.e(TAG, "Failed to remove the user"); + } + if (sp.getAll().isEmpty()) { + file.delete(); + } + } else { + // commit and log the result. + if (!editor.putStringSet(user, packageNames).commit()) { + Slog.e(TAG, "failed to remove the package"); + } + } + checkStageDataAndApplyRestore(packageName, userId); + } } cleanApplicationLocalesIfNeeded(packageName, userId); } @@ -347,16 +375,16 @@ class LocaleManagerBackupHelper { } } - private void checkStageDataAndApplyRestore(String packageName, int uid) { + private void checkStageDataAndApplyRestore(String packageName, int userId) { try { synchronized (mStagedDataLock) { - cleanStagedDataForOldEntriesLocked(); - - int userId = UserHandle.getUserId(uid); - if (mStagedData.contains(userId)) { - if (mPkgsToRestore != null) { - mPkgsToRestore.remove(packageName); + cleanStagedDataForOldEntriesLocked(userId); + if (!getStagedDataSp(userId).getString(packageName, "").isEmpty()) { + if (DEBUG) { + Slog.d(TAG, + "checkStageDataAndApplyRestore, remove package and restore data"); } + removeFromArchivedPackagesInfo(userId, packageName); // Perform lazy restore only if the staged data exists. doLazyRestoreLocked(packageName, userId); } @@ -417,8 +445,17 @@ class LocaleManagerBackupHelper { } } - private void deleteStagedDataLocked(@UserIdInt int userId) { - mStagedData.remove(userId); + void deleteStagedDataLocked(@UserIdInt int userId) { + File stagedFile = getStagedDataFile(userId); + SharedPreferences sp = getStagedDataSp(stagedFile); + // commit and log the result. + if (!sp.edit().clear().commit()) { + Slog.e(TAG, "Failed to commit data!"); + } + + if (stagedFile.exists()) { + stagedFile.delete(); + } } /** @@ -473,16 +510,6 @@ class LocaleManagerBackupHelper { out.endDocument(); } - static class StagedData { - final long mCreationTimeMillis; - final HashMap<String, LocalesInfo> mPackageStates; - - StagedData(long creationTimeMillis, HashMap<String, LocalesInfo> pkgStates) { - mCreationTimeMillis = creationTimeMillis; - mPackageStates = pkgStates; - } - } - static class LocalesInfo { final String mLocales; final boolean mSetFromDelegate; @@ -508,6 +535,7 @@ class LocaleManagerBackupHelper { synchronized (mStagedDataLock) { deleteStagedDataLocked(userId); removeProfileFromPersistedInfo(userId); + removeArchivedPackagesForUser(userId); } } } catch (Exception e) { @@ -533,29 +561,162 @@ class LocaleManagerBackupHelper { return; } - StagedData stagedData = mStagedData.get(userId); - for (String pkgName : stagedData.mPackageStates.keySet()) { - LocalesInfo localesInfo = stagedData.mPackageStates.get(pkgName); + SharedPreferences sp = getStagedDataSp(userId); + String value = sp.getString(packageName, ""); + if (!value.isEmpty()) { + String[] info = value.split(STRING_SPLIT); + if (info == null || info.length != 2) { + Slog.e(TAG, "Failed to restore data"); + return; + } + LocalesInfo localesInfo = new LocalesInfo(info[0], Boolean.parseBoolean(info[1])); + checkExistingLocalesAndApplyRestore(packageName, localesInfo, userId); - if (pkgName.equals(packageName)) { + // Remove the restored entry from the staged data list. + if (!sp.edit().remove(packageName).commit()) { + Slog.e(TAG, "Failed to commit data!"); + } + } - checkExistingLocalesAndApplyRestore(pkgName, localesInfo, userId); + // Remove the stage data entry for user if there are no more packages to restore. + if (sp.getAll().size() == 1 && sp.getLong(KEY_STAGED_DATA_TIME, -1) != -1) { + deleteStagedDataLocked(userId); + } + } - // Remove the restored entry from the staged data list. - stagedData.mPackageStates.remove(pkgName); + private File getStagedDataFile(@UserIdInt int userId) { + return mStagedDataFiles == null ? new File(Environment.getDataSystemDeDirectory(userId), + LOCALES_STAGED_DATA_PREFS) : mStagedDataFiles.get(userId); + } - // Remove the stage data entry for user if there are no more packages to restore. - if (stagedData.mPackageStates.isEmpty()) { - mStagedData.remove(userId); - } + private SharedPreferences getStagedDataSp(File file) { + return mStagedDataFiles == null ? mContext.createDeviceProtectedStorageContext() + .getSharedPreferences(file, Context.MODE_PRIVATE) + : mContext.getSharedPreferences(file, Context.MODE_PRIVATE); + } + + private SharedPreferences getStagedDataSp(@UserIdInt int userId) { + return mStagedDataFiles == null ? mContext.createDeviceProtectedStorageContext() + .getSharedPreferences(getStagedDataFile(userId), Context.MODE_PRIVATE) + : mContext.getSharedPreferences(mStagedDataFiles.get(userId), Context.MODE_PRIVATE); + } - // No need to loop further after restoring locales because the staged data will - // contain at most one entry for the newly added package. - break; + /** + * Store the staged locales info. + */ + private void storeStagedDataInfo(@UserIdInt int userId, @NonNull String packageName, + @NonNull LocalesInfo localesInfo) { + if (DEBUG) { + Slog.d(TAG, "storeStagedDataInfo, userId: " + userId + ", packageName: " + packageName + + ", localesInfo.mLocales: " + localesInfo.mLocales + + ", localesInfo.mSetFromDelegate: " + localesInfo.mSetFromDelegate); + } + String info = + localesInfo.mLocales + STRING_SPLIT + String.valueOf(localesInfo.mSetFromDelegate); + SharedPreferences sp = getStagedDataSp(userId); + // commit and log the result. + if (!sp.edit().putString(packageName, info).commit()) { + Slog.e(TAG, "Failed to commit data!"); + } + } + + /** + * Store the time of creation for staged locales info. + */ + private void storeStagedDataCreatedTime(@UserIdInt int userId) { + SharedPreferences sp = getStagedDataSp(userId); + // commit and log the result. + if (!sp.edit().putLong(KEY_STAGED_DATA_TIME, mClock.millis()).commit()) { + Slog.e(TAG, "Failed to commit data!"); + } + } + + private File getArchivedPackagesFile() { + return mArchivedPackagesFile == null ? new File( + Environment.getDataSystemDeDirectory(UserHandle.USER_SYSTEM), + ARCHIVED_PACKAGES_PREFS) : mArchivedPackagesFile; + } + + private SharedPreferences getArchivedPackagesSp(File file) { + return mArchivedPackagesFile == null ? mContext.createDeviceProtectedStorageContext() + .getSharedPreferences(file, Context.MODE_PRIVATE) + : mContext.getSharedPreferences(file, Context.MODE_PRIVATE); + } + + /** + * Add the package into the archived packages list. + */ + private void addInArchivedPackagesInfo(@UserIdInt int userId, @NonNull String packageName) { + String user = Integer.toString(userId); + SharedPreferences sp = getArchivedPackagesSp(getArchivedPackagesFile()); + Set<String> packageNames = new ArraySet<>(sp.getStringSet(user, new ArraySet<>())); + if (DEBUG) { + Slog.d(TAG, "addInArchivedPackagesInfo before packageNames: " + packageNames + + ", packageName: " + packageName); + } + if (packageNames.add(packageName)) { + // commit and log the result. + if (!sp.edit().putStringSet(user, packageNames).commit()) { + Slog.e(TAG, "failed to add the package"); + } + } + } + + /** + * Remove the package from the archived packages list. + */ + private void removeFromArchivedPackagesInfo(@UserIdInt int userId, + @NonNull String packageName) { + File file = getArchivedPackagesFile(); + if (file.exists()) { + String user = Integer.toString(userId); + SharedPreferences sp = getArchivedPackagesSp(getArchivedPackagesFile()); + Set<String> packageNames = new ArraySet<>(sp.getStringSet(user, new ArraySet<>())); + if (DEBUG) { + Slog.d(TAG, "removeFromArchivedPackagesInfo before packageNames: " + packageNames + + ", packageName: " + packageName); + } + if (packageNames.remove(packageName)) { + SharedPreferences.Editor editor = sp.edit(); + if (packageNames.isEmpty()) { + if (!editor.remove(user).commit()) { + Slog.e(TAG, "Failed to remove user"); + } + if (sp.getAll().isEmpty()) { + file.delete(); + } + } else { + // commit and log the result. + if (!editor.putStringSet(user, packageNames).commit()) { + Slog.e(TAG, "failed to remove the package"); + } + } } } } + /** + * Remove the user from the archived packages list. + */ + private void removeArchivedPackagesForUser(@UserIdInt int userId) { + String user = Integer.toString(userId); + File file = getArchivedPackagesFile(); + SharedPreferences sp = getArchivedPackagesSp(file); + + if (sp == null || !sp.contains(user)) { + Slog.w(TAG, "The profile is not existed in the archived package info"); + return; + } + + if (!sp.edit().remove(user).commit()) { + Slog.e(TAG, "Failed to remove user"); + } + + if (sp.getAll().isEmpty() && file.exists()) { + file.delete(); + } + } + SharedPreferences createPersistedInfo() { final File prefsFile = new File( Environment.getDataSystemDeDirectory(UserHandle.USER_SYSTEM), diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 207707efb51d..ac2c886d1b66 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -28,6 +28,7 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; import static android.os.Process.INVALID_PID; import static android.os.Process.INVALID_UID; +import static android.os.Process.ROOT_UID; import static android.os.Process.SYSTEM_UID; import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER; @@ -385,6 +386,10 @@ public class BackgroundActivityStartController { return BackgroundStartPrivileges.NONE; case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED: // no explicit choice by the app - let us decide what to do + if (callingUid == ROOT_UID || callingUid == SYSTEM_UID) { + // root and system must always opt in explicitly + return BackgroundStartPrivileges.NONE; + } if (callingPackage != null) { // determine based on the calling/creating package boolean changeEnabled = CompatChanges.isChangeEnabled( diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 9dc9ad4a2ba2..6c48e9586fd9 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -1931,6 +1931,9 @@ class Task extends TaskFragment { if (td.getSystemBarsAppearance() == 0) { td.setSystemBarsAppearance(atd.getSystemBarsAppearance()); } + if (td.getTopOpaqueSystemBarsAppearance() == 0 && r.fillsParent()) { + td.setTopOpaqueSystemBarsAppearance(atd.getSystemBarsAppearance()); + } if (td.getNavigationBarColor() == 0) { td.setNavigationBarColor(atd.getNavigationBarColor()); td.setEnsureNavigationBarContrastWhenTransparent( diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 0bf1c88d5b4f..94a22394cf41 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -1525,7 +1525,7 @@ public class WindowManagerService extends IWindowManager.Stub InputChannel outInputChannel, InsetsState outInsetsState, InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame, float[] outSizeCompatScale) { - outActiveControls.set(null); + outActiveControls.set(null, false /* copyControls */); int[] appOp = new int[1]; final boolean isRoundedCornerOverlay = (attrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0; @@ -2317,7 +2317,7 @@ public class WindowManagerService extends IWindowManager.Stub InsetsState outInsetsState, InsetsSourceControl.Array outActiveControls, Bundle outBundle, WindowRelayoutResult outRelayoutResult) { if (outActiveControls != null) { - outActiveControls.set(null); + outActiveControls.set(null, false /* copyControls */); } int result = 0; boolean configChanged = false; @@ -2745,23 +2745,14 @@ public class WindowManagerService extends IWindowManager.Stub private void getInsetsSourceControls(WindowState win, InsetsSourceControl.Array outArray) { final InsetsSourceControl[] controls = win.getDisplayContent().getInsetsStateController().getControlsForDispatch(win); - if (controls != null) { - final int length = controls.length; - final InsetsSourceControl[] outControls = new InsetsSourceControl[length]; - for (int i = 0; i < length; i++) { - // We will leave the critical section before returning the leash to the client, - // so we need to copy the leash to prevent others release the one that we are - // about to return. - if (controls[i] != null) { - // This source control is an extra copy if the client is not local. By setting - // PARCELABLE_WRITE_RETURN_VALUE, the leash will be released at the end of - // SurfaceControl.writeToParcel. - outControls[i] = new InsetsSourceControl(controls[i]); - outControls[i].setParcelableFlags(PARCELABLE_WRITE_RETURN_VALUE); - } - } - outArray.set(outControls); - } + // We will leave the critical section before returning the leash to the client, + // so we need to copy the leash to prevent others release the one that we are + // about to return. + outArray.set(controls, true /* copyControls */); + // This source control is an extra copy if the client is not local. By setting + // PARCELABLE_WRITE_RETURN_VALUE, the leash will be released at the end of + // SurfaceControl.writeToParcel. + outArray.setParcelableFlags(PARCELABLE_WRITE_RETURN_VALUE); } private void tryStartExitingAnimation(WindowState win, WindowStateAnimator winAnimator) { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 6953c60d0d74..d7c49ac81a6c 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -3820,7 +3820,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final InsetsStateController stateController = getDisplayContent().getInsetsStateController(); final InsetsState insetsState = getCompatInsetsState(); - mLastReportedActiveControls.set(stateController.getControlsForDispatch(this)); + mLastReportedActiveControls.set(stateController.getControlsForDispatch(this), + false /* copyControls */); if (Flags.insetsControlChangedItem()) { getProcess().scheduleClientTransactionItem(WindowStateInsetsControlChangeItem.obtain( mClient, insetsState, mLastReportedActiveControls)); diff --git a/services/core/jni/BroadcastRadio/convert.cpp b/services/core/jni/BroadcastRadio/convert.cpp index ddbc5354358c..e42f7f8be0ca 100644 --- a/services/core/jni/BroadcastRadio/convert.cpp +++ b/services/core/jni/BroadcastRadio/convert.cpp @@ -433,7 +433,7 @@ static JavaRef<jobject> BandDescriptorFromHal(JNIEnv *env, const V1_0::BandConfi gjni.AmBandDescriptor.clazz, gjni.AmBandDescriptor.cstor, region, config.type, config.lowerLimit, config.upperLimit, spacing, am.stereo)); } else { - ALOGE("Unsupported band type: %d", config.type); + ALOGE("Unsupported band type: %d", static_cast<int>(config.type)); return nullptr; } } @@ -451,7 +451,7 @@ JavaRef<jobject> BandConfigFromHal(JNIEnv *env, const V1_0::BandConfig &config, return make_javaref(env, env->NewObject( gjni.AmBandConfig.clazz, gjni.AmBandConfig.cstor, descriptor.get())); } else { - ALOGE("Unsupported band type: %d", config.type); + ALOGE("Unsupported band type: %d", static_cast<int>(config.type)); return nullptr; } } @@ -539,9 +539,9 @@ JavaRef<jobject> MetadataFromHal(JNIEnv *env, const hidl_vec<V1_0::MetaData> &me item.clockValue.timezoneOffsetInMinutes); break; default: - ALOGW("invalid metadata type %d", item.type); + ALOGW("invalid metadata type %d", static_cast<int>(item.type)); } - ALOGE_IF(status != 0, "Failed inserting metadata %d (of type %d)", key, item.type); + ALOGE_IF(status != 0, "Failed inserting metadata %d (of type %d)", key, static_cast<int>(item.type)); } return jMetadata; diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java index 12050e1beaed..01ff35fc088c 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -1142,6 +1142,20 @@ public class LocalDisplayAdapterTest { } @Test + public void test_createLocalExternalDisplay_displayManagementEnabled_doesNotCrash() + throws Exception { + FakeDisplay display = new FakeDisplay(PORT_A); + display.info.isInternal = false; + setUpDisplay(display); + updateAvailableDisplays(); + mAdapter.registerLocked(); + when(mSurfaceControlProxy.getDesiredDisplayModeSpecs(display.token)).thenReturn(null); + mInjector.getTransmitter().sendHotplug(display, /* connected */ true); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + assertThat(mListener.addedDisplays.size()).isEqualTo(1); + } + + @Test public void test_createLocalExternalDisplay_displayManagementEnabled_shouldHaveDefaultGroup() throws Exception { FakeDisplay display = new FakeDisplay(PORT_A); diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java index 7dd1847114c8..50cfa753ebdb 100644 --- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java +++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java @@ -20,7 +20,6 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; -import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -52,6 +51,7 @@ import android.util.ArraySet; import android.util.SparseArray; import android.util.Xml; +import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.internal.content.PackageMonitor; @@ -70,6 +70,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; @@ -95,21 +96,21 @@ public class LocaleManagerBackupRestoreTest { private static final int DEFAULT_USER_ID = 0; private static final int WORK_PROFILE_USER_ID = 10; private static final int DEFAULT_UID = Binder.getCallingUid() + 100; + private static final int WORK_PROFILE_UID = Binder.getCallingUid() + 1000100; private static final long DEFAULT_CREATION_TIME_MILLIS = 1000; private static final Duration RETENTION_PERIOD = Duration.ofDays(3); private static final LocaleList DEFAULT_LOCALES = LocaleList.forLanguageTags(DEFAULT_LOCALE_TAGS); private static final Map<String, LocalesInfo> DEFAULT_PACKAGE_LOCALES_INFO_MAP = Map.of( DEFAULT_PACKAGE_NAME, new LocalesInfo(DEFAULT_LOCALE_TAGS, false)); - private static final SparseArray<LocaleManagerBackupHelper.StagedData> STAGE_DATA = - new SparseArray<>(); + private final SparseArray<File> mStagedDataFiles = new SparseArray<>(); + private File mArchivedPackageFile; private LocaleManagerBackupHelper mBackupHelper; private long mCurrentTimeMillis; + private Context mContext = spy(ApplicationProvider.getApplicationContext()); @Mock - private Context mMockContext; - @Mock private PackageManager mMockPackageManager; @Mock private LocaleManagerService mMockLocaleManagerService; @@ -138,23 +139,28 @@ public class LocaleManagerBackupRestoreTest { @Before public void setUp() throws Exception { - mMockContext = mock(Context.class); mMockPackageManager = mock(PackageManager.class); mMockLocaleManagerService = mock(LocaleManagerService.class); mMockDelegateAppLocalePackages = mock(SharedPreferences.class); mMockSpEditor = mock(SharedPreferences.Editor.class); SystemAppUpdateTracker systemAppUpdateTracker = mock(SystemAppUpdateTracker.class); - doReturn(mMockPackageManager).when(mMockContext).getPackageManager(); + doReturn(mMockPackageManager).when(mContext).getPackageManager(); doReturn(mMockSpEditor).when(mMockDelegateAppLocalePackages).edit(); HandlerThread broadcastHandlerThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND); broadcastHandlerThread.start(); - mBackupHelper = spy(new ShadowLocaleManagerBackupHelper(mMockContext, - mMockLocaleManagerService, mMockPackageManager, mClock, STAGE_DATA, - broadcastHandlerThread, mMockDelegateAppLocalePackages)); + File file0 = new File(mContext.getCacheDir(), "file_user_0.txt"); + File file10 = new File(mContext.getCacheDir(), "file_user_10.txt"); + mStagedDataFiles.put(DEFAULT_USER_ID, file0); + mStagedDataFiles.put(WORK_PROFILE_USER_ID, file10); + mArchivedPackageFile = new File(mContext.getCacheDir(), "file_archived.txt"); + + mBackupHelper = spy(new ShadowLocaleManagerBackupHelper(mContext, + mMockLocaleManagerService, mMockPackageManager, mClock, broadcastHandlerThread, + mStagedDataFiles, mArchivedPackageFile, mMockDelegateAppLocalePackages)); doNothing().when(mBackupHelper).notifyBackupManager(); mUserMonitor = mBackupHelper.getUserMonitor(); @@ -165,7 +171,16 @@ public class LocaleManagerBackupRestoreTest { @After public void tearDown() throws Exception { - STAGE_DATA.clear(); + for (int i = 0; i < mStagedDataFiles.size(); i++) { + int userId = mStagedDataFiles.keyAt(i); + File file = mStagedDataFiles.get(userId); + SharedPreferences sp = mContext.getSharedPreferences(file, Context.MODE_PRIVATE); + sp.edit().clear().commit(); + if (file.exists()) { + file.delete(); + } + } + mStagedDataFiles.clear(); } @Test @@ -543,17 +558,21 @@ public class LocaleManagerBackupRestoreTest { mPackageMonitor.onPackageAddedWithExtras(pkgNameA, DEFAULT_UID, bundle); mPackageMonitor.onPackageAddedWithExtras(pkgNameB, DEFAULT_UID, bundle); + checkArchivedFileExists(); + mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID); verifyNothingRestored(); setUpPackageInstalled(pkgNameA); - mPackageMonitor.onPackageUpdateFinished(pkgNameA, DEFAULT_UID); + mBackupHelper.onPackageUpdateFinished(pkgNameA, DEFAULT_UID); verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID, LocaleList.forLanguageTags(langTagsA), false, FrameworkStatsLog .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE); + checkArchivedFileExists(); + mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, pkgNameA, false, false); @@ -565,11 +584,12 @@ public class LocaleManagerBackupRestoreTest { setUpPackageInstalled(pkgNameB); - mPackageMonitor.onPackageUpdateFinished(pkgNameB, DEFAULT_UID); + mBackupHelper.onPackageUpdateFinished(pkgNameB, DEFAULT_UID); verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID, LocaleList.forLanguageTags(langTagsB), true, FrameworkStatsLog .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE); + checkArchivedFileDoesNotExist(); mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, pkgNameB, true, false); @@ -723,7 +743,7 @@ public class LocaleManagerBackupRestoreTest { Intent intent = new Intent(); intent.setAction(Intent.ACTION_USER_REMOVED); intent.putExtra(Intent.EXTRA_USER_HANDLE, DEFAULT_USER_ID); - mUserMonitor.onReceive(mMockContext, intent); + mUserMonitor.onReceive(mContext, intent); // Stage data should be removed only for DEFAULT_USER_ID. checkStageDataDoesNotExist(DEFAULT_USER_ID); @@ -732,6 +752,72 @@ public class LocaleManagerBackupRestoreTest { } @Test + public void testRestore_multipleProfile_restoresFromStage_ArchiveEnabled() throws Exception { + final ByteArrayOutputStream outDefault = new ByteArrayOutputStream(); + writeTestPayload(outDefault, DEFAULT_PACKAGE_LOCALES_INFO_MAP); + final ByteArrayOutputStream outWorkProfile = new ByteArrayOutputStream(); + String anotherPackage = "com.android.anotherapp"; + String anotherLangTags = "mr,zh"; + LocalesInfo localesInfo = new LocalesInfo(anotherLangTags, true); + HashMap<String, LocalesInfo> pkgLocalesMapWorkProfile = new HashMap<>(); + pkgLocalesMapWorkProfile.put(anotherPackage, localesInfo); + writeTestPayload(outWorkProfile, pkgLocalesMapWorkProfile); + // DEFAULT_PACKAGE_NAME is NOT installed on the device. + setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME); + setUpPackageNotInstalled(anotherPackage); + setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.getEmptyLocaleList()); + setUpLocalesForPackage(anotherPackage, LocaleList.getEmptyLocaleList()); + setUpPackageNamesForSp(new ArraySet<>()); + + Bundle bundle = new Bundle(); + bundle.putBoolean(Intent.EXTRA_ARCHIVAL, true); + mPackageMonitor.onPackageAddedWithExtras(DEFAULT_PACKAGE_NAME, DEFAULT_UID, bundle); + mPackageMonitor.onPackageAddedWithExtras(anotherPackage, WORK_PROFILE_UID, bundle); + + checkArchivedFileExists(); + + mBackupHelper.stageAndApplyRestoredPayload(outDefault.toByteArray(), DEFAULT_USER_ID); + mBackupHelper.stageAndApplyRestoredPayload(outWorkProfile.toByteArray(), + WORK_PROFILE_USER_ID); + + verifyNothingRestored(); + verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_INFO_MAP, + DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID); + verifyStageDataForUser(pkgLocalesMapWorkProfile, + DEFAULT_CREATION_TIME_MILLIS, WORK_PROFILE_USER_ID); + + setUpPackageInstalled(DEFAULT_PACKAGE_NAME); + mBackupHelper.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID); + + verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME, + DEFAULT_USER_ID, + LocaleList.forLanguageTags(DEFAULT_LOCALE_TAGS), false, FrameworkStatsLog + .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE); + checkArchivedFileExists(); + checkStageDataDoesNotExist(DEFAULT_USER_ID); + + mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, DEFAULT_PACKAGE_NAME, false, + false); + + verify(mMockSpEditor, times(0)).putStringSet(anyString(), any()); + + setUpPackageInstalled(anotherPackage); + mBackupHelper.onPackageUpdateFinished(anotherPackage, WORK_PROFILE_UID); + + verify(mMockLocaleManagerService, times(1)).setApplicationLocales(anotherPackage, + WORK_PROFILE_USER_ID, + LocaleList.forLanguageTags(anotherLangTags), true, FrameworkStatsLog + .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE); + checkArchivedFileDoesNotExist(); + + mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, anotherPackage, true, false); + + verify(mMockSpEditor, times(1)).putStringSet(Integer.toString(DEFAULT_USER_ID), + new ArraySet<>(Arrays.asList(anotherPackage))); + checkStageDataDoesNotExist(WORK_PROFILE_USER_ID); + } + + @Test public void testPackageRemoved_noInfoInSp() throws Exception { String pkgNameA = "com.android.myAppA"; String pkgNameB = "com.android.myAppB"; @@ -858,10 +944,22 @@ public class LocaleManagerBackupRestoreTest { private void verifyStageDataForUser(Map<String, LocalesInfo> expectedPkgLocalesMap, long expectedCreationTimeMillis, int userId) { - LocaleManagerBackupHelper.StagedData stagedDataForUser = STAGE_DATA.get(userId); - assertNotNull(stagedDataForUser); - assertEquals(expectedCreationTimeMillis, stagedDataForUser.mCreationTimeMillis); - verifyStageData(expectedPkgLocalesMap, stagedDataForUser.mPackageStates); + SharedPreferences sp = mContext.getSharedPreferences(mStagedDataFiles.get(userId), + Context.MODE_PRIVATE); + assertTrue(sp.getAll().size() > 0); + assertEquals(expectedCreationTimeMillis, sp.getLong("staged_data_time", -1)); + verifyStageData(expectedPkgLocalesMap, sp); + } + + private static void verifyStageData(Map<String, LocalesInfo> expectedPkgLocalesMap, + SharedPreferences sp) { + for (String pkg : expectedPkgLocalesMap.keySet()) { + assertTrue(!sp.getString(pkg, "").isEmpty()); + String[] info = sp.getString(pkg, "").split(" s:"); + assertEquals(expectedPkgLocalesMap.get(pkg).mLocales, info[0]); + assertEquals(expectedPkgLocalesMap.get(pkg).mSetFromDelegate, + Boolean.parseBoolean(info[1])); + } } private static void verifyStageData(Map<String, LocalesInfo> expectedPkgLocalesMap, @@ -875,11 +973,19 @@ public class LocaleManagerBackupRestoreTest { } } - private static void checkStageDataExists(int userId) { - assertNotNull(STAGE_DATA.get(userId)); + private void checkStageDataExists(int userId) { + assertTrue(mStagedDataFiles.get(userId) != null && mStagedDataFiles.get(userId).exists()); + } + + private void checkStageDataDoesNotExist(int userId) { + assertTrue(mStagedDataFiles.get(userId) == null || !mStagedDataFiles.get(userId).exists()); + } + + private void checkArchivedFileExists() { + assertTrue(mArchivedPackageFile.exists()); } - private static void checkStageDataDoesNotExist(int userId) { - assertNull(STAGE_DATA.get(userId)); + private void checkArchivedFileDoesNotExist() { + assertTrue(!mArchivedPackageFile.exists()); } -} +}
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java index 9f7cbe3170f0..b46902d9904a 100644 --- a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java +++ b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java @@ -22,6 +22,7 @@ import android.content.pm.PackageManager; import android.os.HandlerThread; import android.util.SparseArray; +import java.io.File; import java.time.Clock; /** @@ -33,9 +34,9 @@ public class ShadowLocaleManagerBackupHelper extends LocaleManagerBackupHelper { ShadowLocaleManagerBackupHelper(Context context, LocaleManagerService localeManagerService, PackageManager packageManager, Clock clock, - SparseArray<LocaleManagerBackupHelper.StagedData> stagedData, - HandlerThread broadcastHandlerThread, SharedPreferences delegateAppLocalePackages) { - super(context, localeManagerService, packageManager, clock, stagedData, - broadcastHandlerThread, delegateAppLocalePackages); + HandlerThread broadcastHandlerThread, SparseArray<File> stagedDataFiles, + File archivedPackagesFile, SharedPreferences delegateAppLocalePackages) { + super(context, localeManagerService, packageManager, clock, broadcastHandlerThread, + stagedDataFiles, archivedPackagesFile, delegateAppLocalePackages); } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 1d014201cf46..a7dbecbb5255 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -794,9 +794,8 @@ public class VoiceInteractionManagerService extends SystemService { if (curService != null && !curService.isEmpty()) { try { serviceComponent = ComponentName.unflattenFromString(curService); - serviceInfo = AppGlobals.getPackageManager() - .getServiceInfo(serviceComponent, 0, mCurUser); - } catch (RuntimeException | RemoteException e) { + serviceInfo = getValidVoiceInteractionServiceInfo(serviceComponent); + } catch (RuntimeException e) { Slog.wtf(TAG, "Bad voice interaction service name " + curService, e); serviceComponent = null; serviceInfo = null; @@ -834,6 +833,27 @@ public class VoiceInteractionManagerService extends SystemService { } } + @Nullable + private ServiceInfo getValidVoiceInteractionServiceInfo( + @Nullable ComponentName serviceComponent) { + if (serviceComponent == null) { + return null; + } + List<ResolveInfo> services = queryInteractorServices( + mCurUser, serviceComponent.getPackageName()); + for (int i = 0; i < services.size(); i++) { + ResolveInfo service = services.get(i); + VoiceInteractionServiceInfo info = new VoiceInteractionServiceInfo( + mContext.getPackageManager(), service.serviceInfo); + ServiceInfo candidateInfo = info.getServiceInfo(); + if (candidateInfo != null + && candidateInfo.getComponentName().equals(serviceComponent)) { + return candidateInfo; + } + } + return null; + } + private List<ResolveInfo> queryInteractorServices( @UserIdInt int user, @Nullable String packageName) { diff --git a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/Utils.kt b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/Utils.kt index 8a241de32a2b..209a14b3657d 100644 --- a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/Utils.kt +++ b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/Utils.kt @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.service import android.app.Instrumentation +import android.platform.test.rule.DisableNotificationCooldownSettingRule import android.platform.test.rule.NavigationModeRule import android.platform.test.rule.PressHomeRule import android.platform.test.rule.UnlockScreenRule @@ -48,6 +49,7 @@ object Utils { clearCacheAfterParsing = false ) ) + .around(DisableNotificationCooldownSettingRule()) .around(PressHomeRule()) } } diff --git a/tests/FlickerTests/IME/Android.bp b/tests/FlickerTests/IME/Android.bp index 3538949cbc8d..ccc3683f0b93 100644 --- a/tests/FlickerTests/IME/Android.bp +++ b/tests/FlickerTests/IME/Android.bp @@ -39,6 +39,10 @@ android_test { defaults: ["FlickerTestsDefault"], manifest: "AndroidManifest.xml", test_config_template: "AndroidTestTemplate.xml", + test_suites: [ + "device-tests", + "device-platinum-tests", + ], srcs: ["src/**/*"], static_libs: ["FlickerTestsBase"], data: ["trace_config/*"], diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt index c29e71ce4c79..07fc2300286a 100644 --- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt +++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt @@ -18,6 +18,7 @@ package com.android.server.wm.flicker.notification import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit +import android.platform.test.rule.DisableNotificationCooldownSettingRule import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.FlickerTestData @@ -37,6 +38,7 @@ import com.android.server.wm.flicker.navBarWindowIsVisibleAtEnd import com.android.server.wm.flicker.taskBarLayerIsVisibleAtEnd import com.android.server.wm.flicker.taskBarWindowIsVisibleAtEnd import org.junit.Assume +import org.junit.ClassRule import org.junit.FixMethodOrder import org.junit.Ignore import org.junit.Test @@ -208,5 +210,10 @@ open class OpenAppFromNotificationWarmTest(flicker: LegacyFlickerTest) : @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams() = LegacyFlickerTestFactory.nonRotationTests() + + /** Ensures that posted notifications will alert and HUN even just after boot. */ + @ClassRule + @JvmField + val disablenotificationCooldown = DisableNotificationCooldownSettingRule() } } |