diff options
40 files changed, 636 insertions, 531 deletions
diff --git a/apex/Android.bp b/apex/Android.bp index e8afa1dabb25..f511af570bb1 100644 --- a/apex/Android.bp +++ b/apex/Android.bp @@ -92,6 +92,9 @@ java_defaults { sdk_version: "module_current", }, + // Configure framework module specific metalava options. + droiddoc_options: [mainline_stubs_args], + // The stub libraries must be visible to frameworks/base so they can be combined // into API specific libraries. stubs_library_visibility: [ diff --git a/apex/permission/framework/Android.bp b/apex/permission/framework/Android.bp index 68c27a8327cb..c43fabde81da 100644 --- a/apex/permission/framework/Android.bp +++ b/apex/permission/framework/Android.bp @@ -21,12 +21,18 @@ filegroup { path: "java", } -java_library { +java_sdk_library { name: "framework-permission", + defaults: ["framework-module-defaults"], srcs: [ ":framework-permission-sources", ], - sdk_version: "module_current", + + // TODO(b/155480189) - Remove naming_scheme once references have been resolved. + // Temporary java_sdk_library component naming scheme to use to ease the transition from separate + // modules to java_sdk_library. + naming_scheme: "framework-modules", + apex_available: [ "com.android.permission", "test_com.android.permission", @@ -40,91 +46,5 @@ java_library { visibility: [ "//frameworks/base/apex/permission:__subpackages__", ], -} - -stubs_defaults { - name: "framework-permission-stubs-defaults", - srcs: [ ":framework-permission-sources" ], - libs: [ "framework-annotations-lib" ], - dist: { dest: "framework-permission.txt" }, -} - -droidstubs { - name: "framework-permission-stubs-srcs-publicapi", - defaults: [ - "framework-module-stubs-defaults-publicapi", - "framework-permission-stubs-defaults", - ], - check_api: { - last_released: { - api_file: ":framework-permission.api.public.latest", - removed_api_file: ":framework-permission-removed.api.public.latest", - }, - api_lint: { - new_since: ":framework-permission.api.public.latest", - }, - }, -} - -droidstubs { - name: "framework-permission-stubs-srcs-systemapi", - defaults: [ - "framework-module-stubs-defaults-systemapi", - "framework-permission-stubs-defaults", - ], - check_api: { - last_released: { - api_file: ":framework-permission.api.system.latest", - removed_api_file: ":framework-permission-removed.api.system.latest", - }, - api_lint: { - new_since: ":framework-permission.api.system.latest", - }, - }, -} - -droidstubs { - name: "framework-permission-api-module_libs_api", - defaults: [ - "framework-module-api-defaults-module_libs_api", - "framework-permission-stubs-defaults", - ], - check_api: { - last_released: { - api_file: ":framework-permission.api.module-lib.latest", - removed_api_file: ":framework-permission-removed.api.module-lib.latest", - }, - api_lint: { - new_since: ":framework-permission.api.module-lib.latest", - }, - }, -} - -droidstubs { - name: "framework-permission-stubs-srcs-module_libs_api", - defaults: [ - "framework-module-stubs-defaults-module_libs_api", - "framework-permission-stubs-defaults", - ], -} - -java_library { - name: "framework-permission-stubs-publicapi", - srcs: [ ":framework-permission-stubs-srcs-publicapi" ], - defaults: ["framework-module-stubs-lib-defaults-publicapi"], - dist: { dest: "framework-permission.jar" }, -} - -java_library { - name: "framework-permission-stubs-systemapi", - srcs: [ ":framework-permission-stubs-srcs-systemapi" ], - defaults: ["framework-module-stubs-lib-defaults-systemapi"], - dist: { dest: "framework-permission.jar" }, -} - -java_library { - name: "framework-permission-stubs-module_libs_api", - srcs: [ ":framework-permission-stubs-srcs-module_libs_api" ], - defaults: ["framework-module-stubs-lib-defaults-module_libs_api"], - dist: { dest: "framework-permission.jar" }, + stubs_library_visibility: ["//visibility:public"], } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index a1a11ed54609..16e5156c10de 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -226,9 +226,10 @@ public class StorageManager { * <p> * This intent should be launched using * {@link Activity#startActivityForResult(Intent, int)} so that the user - * knows which app is requesting to clear cache. The returned result will - * be {@link Activity#RESULT_OK} if the activity was launched and the user accepted to clear - * cache, or {@link Activity#RESULT_CANCELED} otherwise. + * knows which app is requesting to clear cache. The returned result will be: + * {@link Activity#RESULT_OK} if the activity was launched and all cache was cleared, + * {@link OsConstants#EIO} if an error occurred while clearing the cache or + * {@link Activity#RESULT_CANCELED} otherwise. */ @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE) @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) diff --git a/core/java/com/android/internal/BrightnessSynchronizer.java b/core/java/com/android/internal/BrightnessSynchronizer.java index aa23251b9b85..42724bede481 100644 --- a/core/java/com/android/internal/BrightnessSynchronizer.java +++ b/core/java/com/android/internal/BrightnessSynchronizer.java @@ -84,17 +84,17 @@ public class BrightnessSynchronizer{ * Converts between the int brightness system and the float brightness system. */ public static float brightnessIntToFloat(Context context, int brightnessInt) { - PowerManager pm = context.getSystemService(PowerManager.class); - float pmMinBrightness = pm.getBrightnessConstraint( + final PowerManager pm = context.getSystemService(PowerManager.class); + final float pmMinBrightness = pm.getBrightnessConstraint( PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM); - float pmMaxBrightness = pm.getBrightnessConstraint( + final float pmMaxBrightness = pm.getBrightnessConstraint( PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM); - int minBrightnessInt = brightnessFloatToInt(pmMinBrightness, PowerManager.BRIGHTNESS_MIN, - PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_OFF + 1, - PowerManager.BRIGHTNESS_ON); - int maxBrightnessInt = brightnessFloatToInt(pmMaxBrightness, PowerManager.BRIGHTNESS_MIN, - PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_OFF + 1, - PowerManager.BRIGHTNESS_ON); + final int minBrightnessInt = Math.round(brightnessFloatToIntRange(pmMinBrightness, + PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, + PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON)); + final int maxBrightnessInt = Math.round(brightnessFloatToIntRange(pmMaxBrightness, + PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, + PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON)); return brightnessIntToFloat(brightnessInt, minBrightnessInt, maxBrightnessInt, pmMinBrightness, pmMaxBrightness); @@ -119,34 +119,43 @@ public class BrightnessSynchronizer{ * Converts between the float brightness system and the int brightness system. */ public static int brightnessFloatToInt(Context context, float brightnessFloat) { - PowerManager pm = context.getSystemService(PowerManager.class); - float pmMinBrightness = pm.getBrightnessConstraint( + return Math.round(brightnessFloatToIntRange(context, brightnessFloat)); + } + + /** + * Converts between the float brightness system and the int brightness system, but returns + * the converted value as a float within the int-system's range. This method helps with + * conversions from one system to the other without losing the floating-point precision. + */ + public static float brightnessFloatToIntRange(Context context, float brightnessFloat) { + final PowerManager pm = context.getSystemService(PowerManager.class); + final float minFloat = pm.getBrightnessConstraint( PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM); - float pmMaxBrightness = pm.getBrightnessConstraint( + final float maxFloat = pm.getBrightnessConstraint( PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM); - int minBrightnessInt = brightnessFloatToInt(pmMinBrightness, PowerManager.BRIGHTNESS_MIN, - PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_OFF + 1, - PowerManager.BRIGHTNESS_ON); - int maxBrightnessInt = brightnessFloatToInt(pmMaxBrightness, PowerManager.BRIGHTNESS_MIN, - PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_OFF + 1, - PowerManager.BRIGHTNESS_ON); - - return brightnessFloatToInt(brightnessFloat, pmMinBrightness, pmMaxBrightness, - minBrightnessInt, maxBrightnessInt); + final float minInt = brightnessFloatToIntRange(minFloat, + PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, + PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON); + final float maxInt = brightnessFloatToIntRange(maxFloat, + PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, + PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON); + return brightnessFloatToIntRange(brightnessFloat, minFloat, maxFloat, minInt, maxInt); } /** - * Converts between the float brightness system and the int brightness system. + * Translates specified value from the float brightness system to the int brightness system, + * given the min/max of each range. Accounts for special values such as OFF and invalid values. + * Value returned as a float privimite (to preserve precision), but is a value within the + * int-system range. */ - public static int brightnessFloatToInt(float brightnessFloat, float minFloat, float maxFloat, - int minInt, int maxInt) { + private static float brightnessFloatToIntRange(float brightnessFloat, float minFloat, + float maxFloat, float minInt, float maxInt) { if (floatEquals(brightnessFloat, PowerManager.BRIGHTNESS_OFF_FLOAT)) { return PowerManager.BRIGHTNESS_OFF; } else if (Float.isNaN(brightnessFloat)) { return PowerManager.BRIGHTNESS_INVALID; } else { - return Math.round(MathUtils.constrainedMap((float) minInt, (float) maxInt, minFloat, - maxFloat, brightnessFloat)); + return MathUtils.constrainedMap(minInt, maxInt, minFloat, maxFloat, brightnessFloat); } } diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 54d14f892e25..233f72eaae1f 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -451,10 +451,14 @@ <string name="personal_apps_suspension_text"> Your personal apps are blocked until you turn on your work profile</string> <!-- Notification text. This notification lets a user know that their apps will be blocked - tomorrow due to a work policy from their IT admin, and that they need to turn on their work - profile to prevent the apps from being blocked. [CHAR LIMIT=NONE] --> - <string name="personal_apps_suspension_tomorrow_text"> - Your personal apps will be blocked tomorrow</string> + at a particular time due to a work policy from their IT admin, and that they need to turn on + their work profile to prevent the apps from being blocked. It also explains for how many + days the profile is allowed to be off and this number is at least 3. [CHAR LIMIT=NONE] --> + <string name="personal_apps_suspension_soon_text"> + Personal apps will be blocked on <xliff:g id="date" example="May 29">%1$s</xliff:g> at + <xliff:g id="time" example="5:20 PM">%2$s</xliff:g>. Your work profile can\u2019t stay off + for more than <xliff:g id="number" example="3">%3$d</xliff:g> days. + </string> <!-- Title for the button that turns work profile on. To be used in a notification [CHAR LIMIT=NONE] --> <string name="personal_apps_suspended_turn_profile_on">Turn on work profile</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 64905ec9db7a..b483ee04a7ec 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1200,7 +1200,7 @@ <java-symbol type="string" name="location_changed_notification_title" /> <java-symbol type="string" name="location_changed_notification_text" /> <java-symbol type="string" name="personal_apps_suspension_title" /> - <java-symbol type="string" name="personal_apps_suspension_tomorrow_text" /> + <java-symbol type="string" name="personal_apps_suspension_soon_text" /> <java-symbol type="string" name="personal_apps_suspension_text" /> <java-symbol type="string" name="personal_apps_suspended_turn_profile_on" /> <java-symbol type="string" name="notification_work_profile_content_description" /> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index e670f1f83a52..4510b87556c7 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -685,6 +685,7 @@ </activity> <activity android:name=".controls.management.ControlsEditingActivity" + android:label="@string/controls_menu_edit" android:theme="@style/Theme.ControlsManagement" android:excludeFromRecents="true" android:noHistory="true" diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 76ca385bd9d9..09918e764140 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -169,5 +169,8 @@ <item type="id" name="screen_recording_options" /> <item type="id" name="screen_recording_dialog_source_text" /> <item type="id" name="screen_recording_dialog_source_description" /> + + <item type="id" name="accessibility_action_controls_move_before" /> + <item type="id" name="accessibility_action_controls_move_after" /> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 8c10f61db7a0..8bbcfa0e7898 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2711,6 +2711,8 @@ <string name="accessibility_control_change_favorite">favorite</string> <!-- a11y action to unfavorite a control. It will read as "Double-tap to unfavorite" in screen readers [CHAR LIMIT=NONE] --> <string name="accessibility_control_change_unfavorite">unfavorite</string> + <!-- a11y action to move a control to the position specified by the parameter [CHAR LIMIT=NONE] --> + <string name="accessibility_control_move">Move to position <xliff:g id="number" example="1">%d</xliff:g></string> <!-- Controls management controls screen default title [CHAR LIMIT=30] --> <string name="controls_favorite_default_title">Controls</string> diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java index f0443892bfaf..f1b401e77fbc 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java @@ -16,7 +16,6 @@ package com.android.systemui.bubbles; -import static android.app.Notification.FLAG_BUBBLE; import static android.os.AsyncTask.Status.FINISHED; import static android.view.Display.INVALID_DISPLAY; @@ -28,7 +27,6 @@ import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; -import android.content.pm.ApplicationInfo; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; @@ -57,11 +55,6 @@ import java.util.Objects; class Bubble implements BubbleViewProvider { private static final String TAG = "Bubble"; - /** - * NotificationEntry associated with the bubble. A null value implies this bubble is loaded - * from disk. - */ - @Nullable private NotificationEntry mEntry; private final String mKey; private final String mGroupId; @@ -102,56 +95,18 @@ class Bubble implements BubbleViewProvider { private Bitmap mBadgedImage; private int mDotColor; private Path mDotPath; - private int mFlags; - /** - * Extract GroupId from {@link NotificationEntry}. See also {@link #groupId(ShortcutInfo)}. - */ + public static String groupId(NotificationEntry entry) { UserHandle user = entry.getSbn().getUser(); return user.getIdentifier() + "|" + entry.getSbn().getPackageName(); } - /** - * Extract GroupId from {@link ShortcutInfo}. This should match the one generated from - * {@link NotificationEntry}. See also {@link #groupId(NotificationEntry)}. - */ - @NonNull - public static String groupId(@NonNull final ShortcutInfo shortcutInfo) { - return shortcutInfo.getUserId() + "|" + shortcutInfo.getPackage(); - } - - /** - * Generate a unique identifier for this bubble based on given {@link NotificationEntry}. If - * {@link ShortcutInfo} was found in the notification entry, the identifier would be generated - * from {@link ShortcutInfo} instead. See also {@link #key(ShortcutInfo)}. - */ - @NonNull - public static String key(@NonNull final NotificationEntry entry) { - final ShortcutInfo shortcutInfo = entry.getRanking().getShortcutInfo(); - if (shortcutInfo != null) return key(shortcutInfo); - return entry.getKey(); - } - - /** - * Generate a unique identifier for this bubble based on given {@link ShortcutInfo}. - * See also {@link #key(NotificationEntry)}. - */ - @NonNull - public static String key(@NonNull final ShortcutInfo shortcutInfo) { - return shortcutInfo.getUserId() + "|" + shortcutInfo.getPackage() + "|" - + shortcutInfo.getId(); - } - - /** - * Create a bubble with limited information based on given {@link ShortcutInfo}. - * Note: Currently this is only being used when the bubble is persisted to disk. - */ + // TODO: Decouple Bubble from NotificationEntry and transform ShortcutInfo into Bubble Bubble(ShortcutInfo shortcutInfo) { mShortcutInfo = shortcutInfo; - mKey = key(shortcutInfo); - mGroupId = groupId(shortcutInfo); - mFlags = 0; + mKey = shortcutInfo.getId(); + mGroupId = shortcutInfo.getId(); } /** Used in tests when no UI is required. */ @@ -159,11 +114,10 @@ class Bubble implements BubbleViewProvider { Bubble(NotificationEntry e, BubbleController.NotificationSuppressionChangedListener listener) { mEntry = e; - mKey = key(e); + mKey = e.getKey(); mLastUpdated = e.getSbn().getPostTime(); mGroupId = groupId(e); mSuppressionListener = listener; - mFlags = e.getSbn().getNotification().flags; } @Override @@ -171,26 +125,16 @@ class Bubble implements BubbleViewProvider { return mKey; } - @Nullable public NotificationEntry getEntry() { return mEntry; } - @Nullable - public UserHandle getUser() { - if (mEntry != null) return mEntry.getSbn().getUser(); - if (mShortcutInfo != null) return mShortcutInfo.getUserHandle(); - return null; - } - public String getGroupId() { return mGroupId; } public String getPackageName() { - return mEntry == null - ? mShortcutInfo == null ? null : mShortcutInfo.getPackage() - : mEntry.getSbn().getPackageName(); + return mEntry.getSbn().getPackageName(); } @Override @@ -274,8 +218,7 @@ class Bubble implements BubbleViewProvider { void inflate(BubbleViewInfoTask.Callback callback, Context context, BubbleStackView stackView, - BubbleIconFactory iconFactory, - boolean skipInflation) { + BubbleIconFactory iconFactory) { if (isBubbleLoading()) { mInflationTask.cancel(true /* mayInterruptIfRunning */); } @@ -283,7 +226,6 @@ class Bubble implements BubbleViewProvider { context, stackView, iconFactory, - skipInflation, callback); if (mInflateSynchronously) { mInflationTask.onPostExecute(mInflationTask.doInBackground()); @@ -403,7 +345,6 @@ class Bubble implements BubbleViewProvider { * Whether this notification should be shown in the shade. */ boolean showInShade() { - if (mEntry == null) return false; return !shouldSuppressNotification() || !mEntry.isClearable(); } @@ -411,8 +352,8 @@ class Bubble implements BubbleViewProvider { * Sets whether this notification should be suppressed in the shade. */ void setSuppressNotification(boolean suppressNotification) { - if (mEntry == null) return; boolean prevShowInShade = showInShade(); + Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); int flags = data.getFlags(); if (suppressNotification) { @@ -443,7 +384,6 @@ class Bubble implements BubbleViewProvider { */ @Override public boolean showDot() { - if (mEntry == null) return false; return mShowBubbleUpdateDot && !mEntry.shouldSuppressNotificationDot() && !shouldSuppressNotification(); @@ -453,7 +393,6 @@ class Bubble implements BubbleViewProvider { * Whether the flyout for the bubble should be shown. */ boolean showFlyout() { - if (mEntry == null) return false; return !mSuppressFlyout && !mEntry.shouldSuppressPeek() && !shouldSuppressNotification() && !mEntry.shouldSuppressNotificationList(); @@ -477,13 +416,11 @@ class Bubble implements BubbleViewProvider { * is an ongoing bubble. */ boolean isOngoing() { - if (mEntry == null) return false; int flags = mEntry.getSbn().getNotification().flags; return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0; } float getDesiredHeight(Context context) { - if (mEntry == null) return 0; Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); boolean useRes = data.getDesiredHeightResId() != 0; if (useRes) { @@ -497,7 +434,6 @@ class Bubble implements BubbleViewProvider { } String getDesiredHeightString() { - if (mEntry == null) return String.valueOf(0); Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); boolean useRes = data.getDesiredHeightResId() != 0; if (useRes) { @@ -514,13 +450,11 @@ class Bubble implements BubbleViewProvider { * To populate the icon use {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)}. */ boolean usingShortcutInfo() { - return mEntry != null && mEntry.getBubbleMetadata().getShortcutId() != null - || mShortcutInfo != null; + return mEntry.getBubbleMetadata().getShortcutId() != null; } @Nullable PendingIntent getBubbleIntent() { - if (mEntry == null) return null; Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); if (data != null) { return data.getIntent(); @@ -528,32 +462,16 @@ class Bubble implements BubbleViewProvider { return null; } - Intent getSettingsIntent(final Context context) { + Intent getSettingsIntent() { final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS); intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName()); - final int uid = getUid(context); - if (uid != -1) { - intent.putExtra(Settings.EXTRA_APP_UID, uid); - } + intent.putExtra(Settings.EXTRA_APP_UID, mEntry.getSbn().getUid()); intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); return intent; } - private int getUid(final Context context) { - if (mEntry != null) return mEntry.getSbn().getUid(); - final PackageManager pm = context.getPackageManager(); - if (pm == null) return -1; - try { - final ApplicationInfo info = pm.getApplicationInfo(mShortcutInfo.getPackage(), 0); - return info.uid; - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "cannot find uid", e); - } - return -1; - } - private int getDimenForPackageUser(Context context, int resId, String pkg, int userId) { PackageManager pm = context.getPackageManager(); Resources r; @@ -575,30 +493,15 @@ class Bubble implements BubbleViewProvider { } private boolean shouldSuppressNotification() { - if (mEntry == null) return false; return mEntry.getBubbleMetadata() != null && mEntry.getBubbleMetadata().isNotificationSuppressed(); } boolean shouldAutoExpand() { - if (mEntry == null) return false; Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata(); return metadata != null && metadata.getAutoExpandBubble(); } - public boolean isBubble() { - if (mEntry == null) return (mFlags & FLAG_BUBBLE) != 0; - return (mEntry.getSbn().getNotification().flags & FLAG_BUBBLE) != 0; - } - - public void enable(int option) { - mFlags |= option; - } - - public void disable(int option) { - mFlags &= ~option; - } - @Override public String toString() { return "Bubble{" + mKey + '}'; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 8707d389858b..a578f337cca2 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -42,7 +42,6 @@ import static java.lang.annotation.ElementType.LOCAL_VARIABLE; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.SOURCE; -import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.ActivityManager.RunningTaskInfo; import android.app.INotificationManager; @@ -243,7 +242,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * This can happen when an app cancels a bubbled notification or when the user dismisses a * bubble. */ - void removeNotification(@NonNull NotificationEntry entry, int reason); + void removeNotification(NotificationEntry entry, int reason); /** * Called when a bubbled notification has changed whether it should be @@ -259,7 +258,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * removes all remnants of the group's summary from the notification pipeline. * TODO: (b/145659174) Only old pipeline needs this - delete post-migration. */ - void maybeCancelSummary(@NonNull NotificationEntry entry); + void maybeCancelSummary(NotificationEntry entry); } /** @@ -482,7 +481,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi addNotifCallback(new NotifCallback() { @Override - public void removeNotification(@NonNull final NotificationEntry entry, int reason) { + public void removeNotification(NotificationEntry entry, int reason) { mNotificationEntryManager.performRemoveNotification(entry.getSbn(), reason); } @@ -493,7 +492,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } @Override - public void maybeCancelSummary(@NonNull final NotificationEntry entry) { + public void maybeCancelSummary(NotificationEntry entry) { // Check if removed bubble has an associated suppressed group summary that needs // to be removed now. final String groupKey = entry.getSbn().getGroupKey(); @@ -702,12 +701,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mBubbleIconFactory = new BubbleIconFactory(mContext); // Reload each bubble for (Bubble b: mBubbleData.getBubbles()) { - b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory, - false /* skipInflation */); + b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory); } for (Bubble b: mBubbleData.getOverflowBubbles()) { - b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory, - false /* skipInflation */); + b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory); } } @@ -806,7 +803,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (bubble != null) { mBubbleData.promoteBubbleFromOverflow(bubble, mStackView, mBubbleIconFactory); } - } else if (bubble.isBubble()) { + } else if (bubble.getEntry().isBubble()){ mBubbleData.setSelectedBubble(bubble); } mBubbleData.setExpanded(true); @@ -835,33 +832,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi updateBubble(notif, suppressFlyout, true /* showInShade */); } - /** - * Fills the overflow bubbles by loading them from disk. - */ - void loadOverflowBubblesFromDisk() { - if (!mBubbleData.getOverflowBubbles().isEmpty()) { - // we don't need to load overflow bubbles from disk if it is already in memory - return; - } - mDataRepository.loadBubbles((bubbles) -> { - bubbles.forEach(bubble -> { - if (mBubbleData.getBubbles().contains(bubble)) { - // if the bubble is already active, there's no need to push it to overflow - return; - } - bubble.inflate((b) -> mBubbleData.overflowBubble(DISMISS_AGED, bubble), - mContext, mStackView, mBubbleIconFactory, true /* skipInflation */); - }); - return null; - }); - } - void updateBubble(NotificationEntry notif, boolean suppressFlyout, boolean showInShade) { if (mStackView == null) { // Lazy init stack view when a bubble is created ensureStackViewCreated(); - // Lazy load overflow bubbles from disk - loadOverflowBubblesFromDisk(); } // If this is an interruptive notif, mark that it's interrupted if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) { @@ -881,11 +855,11 @@ public class BubbleController implements ConfigurationController.ConfigurationLi return; } mHandler.post( - () -> removeBubble(bubble.getKey(), + () -> removeBubble(bubble.getEntry(), BubbleController.DISMISS_INVALID_INTENT)); }); }, - mContext, mStackView, mBubbleIconFactory, false /* skipInflation */); + mContext, mStackView, mBubbleIconFactory); } /** @@ -897,10 +871,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * @param entry the notification to change bubble state for. * @param shouldBubble whether the notification should show as a bubble or not. */ - public void onUserChangedBubble(@Nullable final NotificationEntry entry, boolean shouldBubble) { - if (entry == null) { - return; - } + public void onUserChangedBubble(NotificationEntry entry, boolean shouldBubble) { NotificationChannel channel = entry.getChannel(); final String appPkg = entry.getSbn().getPackageName(); final int appUid = entry.getSbn().getUid(); @@ -939,14 +910,14 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } /** - * Removes the bubble with the given key. + * Removes the bubble with the given NotificationEntry. * <p> * Must be called from the main thread. */ @MainThread - void removeBubble(String key, int reason) { - if (mBubbleData.hasAnyBubbleWithKey(key)) { - mBubbleData.notificationEntryRemoved(key, reason); + void removeBubble(NotificationEntry entry, int reason) { + if (mBubbleData.hasAnyBubbleWithKey(entry.getKey())) { + mBubbleData.notificationEntryRemoved(entry, reason); } } @@ -962,7 +933,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi && canLaunchInActivityView(mContext, entry); if (!shouldBubble && mBubbleData.hasAnyBubbleWithKey(entry.getKey())) { // It was previously a bubble but no longer a bubble -- lets remove it - removeBubble(entry.getKey(), DISMISS_NO_LONGER_BUBBLE); + removeBubble(entry, DISMISS_NO_LONGER_BUBBLE); } else if (shouldBubble) { updateBubble(entry); } @@ -976,10 +947,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // Remove any associated bubble children with the summary final List<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey); for (int i = 0; i < bubbleChildren.size(); i++) { - removeBubble(bubbleChildren.get(i).getKey(), DISMISS_GROUP_CANCELLED); + removeBubble(bubbleChildren.get(i).getEntry(), DISMISS_GROUP_CANCELLED); } } else { - removeBubble(entry.getKey(), DISMISS_NOTIF_CANCEL); + removeBubble(entry, DISMISS_NOTIF_CANCEL); } } @@ -1001,8 +972,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi rankingMap.getRanking(key, mTmpRanking); boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key); if (isActiveBubble && !mTmpRanking.canBubble()) { - mBubbleData.notificationEntryRemoved(entry.getKey(), - BubbleController.DISMISS_BLOCKED); + mBubbleData.notificationEntryRemoved(entry, BubbleController.DISMISS_BLOCKED); } else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble) { entry.setFlagBubble(true); onEntryUpdated(entry); @@ -1012,15 +982,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private void setIsBubble(Bubble b, boolean isBubble) { if (isBubble) { - if (b.getEntry() != null) { - b.getEntry().getSbn().getNotification().flags |= FLAG_BUBBLE; - } - b.enable(FLAG_BUBBLE); + b.getEntry().getSbn().getNotification().flags |= FLAG_BUBBLE; } else { - if (b.getEntry() != null) { - b.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE; - } - b.disable(FLAG_BUBBLE); + b.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE; } try { mBarService.onNotificationBubbleChanged(b.getKey(), isBubble, 0); @@ -1068,27 +1032,23 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // The bubble is now gone & the notification is hidden from the shade, so // time to actually remove it for (NotifCallback cb : mCallbacks) { - if (bubble.getEntry() != null) { - cb.removeNotification(bubble.getEntry(), REASON_CANCEL); - } + cb.removeNotification(bubble.getEntry(), REASON_CANCEL); } } else { - if (bubble.isBubble() && bubble.showInShade()) { + if (bubble.getEntry().isBubble() && bubble.showInShade()) { setIsBubble(bubble, /* isBubble */ false); } - if (bubble.getEntry() != null && bubble.getEntry().getRow() != null) { + if (bubble.getEntry().getRow() != null) { bubble.getEntry().getRow().updateBubbleButton(); } } } - if (bubble.getEntry() != null) { - final String groupKey = bubble.getEntry().getSbn().getGroupKey(); - if (mBubbleData.getBubblesInGroup(groupKey).isEmpty()) { - // Time to potentially remove the summary - for (NotifCallback cb : mCallbacks) { - cb.maybeCancelSummary(bubble.getEntry()); - } + final String groupKey = bubble.getEntry().getSbn().getGroupKey(); + if (mBubbleData.getBubblesInGroup(groupKey).isEmpty()) { + // Time to potentially remove the summary + for (NotifCallback cb : mCallbacks) { + cb.maybeCancelSummary(bubble.getEntry()); } } } @@ -1113,7 +1073,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (update.selectionChanged) { mStackView.setSelectedBubble(update.selectedBubble); - if (update.selectedBubble != null && update.selectedBubble.getEntry() != null) { + if (update.selectedBubble != null) { mNotificationGroupManager.updateSuppression( update.selectedBubble.getEntry()); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index 7b323ce8f609..65d5bebc625d 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -23,7 +23,6 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME import static java.util.stream.Collectors.toList; -import android.annotation.NonNull; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; @@ -224,7 +223,7 @@ public class BubbleData { false, /* showInShade */ true); setSelectedBubble(bubble); }, - mContext, stack, factory, false /* skipInflation */); + mContext, stack, factory); dispatchPendingChanges(); } @@ -279,8 +278,7 @@ public class BubbleData { } mPendingBubbles.remove(bubble); // No longer pending once we're here Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey()); - suppressFlyout |= bubble.getEntry() == null - || !bubble.getEntry().getRanking().visuallyInterruptive(); + suppressFlyout |= !bubble.getEntry().getRanking().visuallyInterruptive(); if (prevBubble == null) { // Create a new bubble @@ -309,14 +307,11 @@ public class BubbleData { dispatchPendingChanges(); } - /** - * Called when a notification associated with a bubble is removed. - */ - public void notificationEntryRemoved(String key, @DismissReason int reason) { + public void notificationEntryRemoved(NotificationEntry entry, @DismissReason int reason) { if (DEBUG_BUBBLE_DATA) { - Log.d(TAG, "notificationEntryRemoved: key=" + key + " reason=" + reason); + Log.d(TAG, "notificationEntryRemoved: entry=" + entry + " reason=" + reason); } - doRemove(key, reason); + doRemove(entry.getKey(), reason); dispatchPendingChanges(); } @@ -364,7 +359,7 @@ public class BubbleData { return bubbleChildren; } for (Bubble b : mBubbles) { - if (b.getEntry() != null && groupKey.equals(b.getEntry().getSbn().getGroupKey())) { + if (groupKey.equals(b.getEntry().getSbn().getGroupKey())) { bubbleChildren.add(b); } } @@ -475,9 +470,7 @@ public class BubbleData { Bubble newSelected = mBubbles.get(newIndex); setSelectedBubbleInternal(newSelected); } - if (bubbleToRemove.getEntry() != null) { - maybeSendDeleteIntent(reason, bubbleToRemove.getEntry()); - } + maybeSendDeleteIntent(reason, bubbleToRemove.getEntry()); } void overflowBubble(@DismissReason int reason, Bubble bubble) { @@ -751,8 +744,7 @@ public class BubbleData { return true; } - private void maybeSendDeleteIntent(@DismissReason int reason, - @NonNull final NotificationEntry entry) { + private void maybeSendDeleteIntent(@DismissReason int reason, NotificationEntry entry) { if (reason == BubbleController.DISMISS_USER_GESTURE) { Notification.BubbleMetadata bubbleMetadata = entry.getBubbleMetadata(); PendingIntent deleteIntent = bubbleMetadata != null diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt index 6df313212c76..ba93f4125ea5 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt @@ -74,10 +74,7 @@ internal class BubbleDataRepository @Inject constructor( private fun transform(userId: Int, bubbles: List<Bubble>): List<BubbleEntity> { return bubbles.mapNotNull { b -> - var shortcutId = b.shortcutInfo?.id - if (shortcutId == null) shortcutId = b.entry?.bubbleMetadata?.shortcutId - if (shortcutId == null) shortcutId = b.entry?.ranking?.shortcutInfo?.id - if (shortcutId == null) return@mapNotNull null + val shortcutId = b.shortcutInfo?.id ?: return@mapNotNull null BubbleEntity(userId, b.packageName, shortcutId) } } @@ -111,6 +108,7 @@ internal class BubbleDataRepository @Inject constructor( /** * Load bubbles from disk. */ + // TODO: call this method from BubbleController and update UI @SuppressLint("WrongConstant") fun loadBubbles(cb: (List<Bubble>) -> Unit) = ioScope.launch { /** diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index 0a1a0f77b32e..baf92dc7abe7 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -65,6 +65,7 @@ import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.recents.TriangleShape; import com.android.systemui.statusbar.AlphaOptimizedButton; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** * Container for the expanded bubble view, handles rendering the caret and settings icon. @@ -160,7 +161,7 @@ public class BubbleExpandedView extends LinearLayout { // the bubble again so we'll just remove it. Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey() + ", " + e.getMessage() + "; removing bubble"); - mBubbleController.removeBubble(getBubbleKey(), + mBubbleController.removeBubble(getBubbleEntry(), BubbleController.DISMISS_INVALID_INTENT); } }); @@ -204,7 +205,7 @@ public class BubbleExpandedView extends LinearLayout { } if (mBubble != null) { // Must post because this is called from a binder thread. - post(() -> mBubbleController.removeBubble(mBubble.getKey(), + post(() -> mBubbleController.removeBubble(mBubble.getEntry(), BubbleController.DISMISS_TASK_FINISHED)); } } @@ -291,6 +292,10 @@ public class BubbleExpandedView extends LinearLayout { return mBubble != null ? mBubble.getKey() : "null"; } + private NotificationEntry getBubbleEntry() { + return mBubble != null ? mBubble.getEntry() : null; + } + void setManageClickListener(OnClickListener manageClickListener) { findViewById(R.id.settings_button).setOnClickListener(manageClickListener); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index e35b20392380..88f5eb0b250c 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -49,6 +49,7 @@ import android.graphics.RectF; import android.graphics.Region; import android.os.Bundle; import android.os.Vibrator; +import android.service.notification.StatusBarNotification; import android.util.Log; import android.view.Choreographer; import android.view.DisplayCutout; @@ -918,10 +919,10 @@ public class BubbleStackView extends FrameLayout showManageMenu(false /* show */); final Bubble bubble = mBubbleData.getSelectedBubble(); if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) { - final Intent intent = bubble.getSettingsIntent(mContext); + final Intent intent = bubble.getSettingsIntent(); collapseStack(() -> { - - mContext.startActivityAsUser(intent, bubble.getUser()); + mContext.startActivityAsUser( + intent, bubble.getEntry().getSbn().getUser()); logBubbleClickEvent( bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS); @@ -1178,19 +1179,13 @@ public class BubbleStackView extends FrameLayout for (int i = 0; i < mBubbleData.getBubbles().size(); i++) { final Bubble bubble = mBubbleData.getBubbles().get(i); final String appName = bubble.getAppName(); + final Notification notification = bubble.getEntry().getSbn().getNotification(); + final CharSequence titleCharSeq = + notification.extras.getCharSequence(Notification.EXTRA_TITLE); - final CharSequence titleCharSeq; - if (bubble.getEntry() == null) { - titleCharSeq = null; - } else { - titleCharSeq = bubble.getEntry().getSbn().getNotification().extras.getCharSequence( - Notification.EXTRA_TITLE); - } - final String titleStr; + String titleStr = getResources().getString(R.string.notification_bubble_title); if (titleCharSeq != null) { titleStr = titleCharSeq.toString(); - } else { - titleStr = getResources().getString(R.string.notification_bubble_title); } if (bubble.getIconView() != null) { @@ -1803,7 +1798,7 @@ public class BubbleStackView extends FrameLayout private void dismissBubbleIfExists(@Nullable Bubble bubble) { if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) { mBubbleData.notificationEntryRemoved( - bubble.getKey(), BubbleController.DISMISS_USER_GESTURE); + bubble.getEntry(), BubbleController.DISMISS_USER_GESTURE); } } @@ -2301,12 +2296,18 @@ public class BubbleStackView extends FrameLayout * @param action the user interaction enum. */ private void logBubbleClickEvent(Bubble bubble, int action) { - bubble.logUIEvent( + StatusBarNotification notification = bubble.getEntry().getSbn(); + SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, + notification.getPackageName(), + notification.getNotification().getChannelId(), + notification.getId(), + getBubbleIndex(getExpandedBubble()), getBubbleCount(), action, getNormalizedXPosition(), getNormalizedYPosition(), - getBubbleIndex(getExpandedBubble()) - ); + bubble.showInShade(), + bubble.isOngoing(), + false /* isAppForeground (unused) */); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java index 525d5b56cc8e..8a57a735f6cb 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java @@ -37,7 +37,6 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.AsyncTask; import android.os.Parcelable; -import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.util.Log; @@ -75,7 +74,6 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask private WeakReference<Context> mContext; private WeakReference<BubbleStackView> mStackView; private BubbleIconFactory mIconFactory; - private boolean mSkipInflation; private Callback mCallback; /** @@ -86,20 +84,17 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask Context context, BubbleStackView stackView, BubbleIconFactory factory, - boolean skipInflation, Callback c) { mBubble = b; mContext = new WeakReference<>(context); mStackView = new WeakReference<>(stackView); mIconFactory = factory; - mSkipInflation = skipInflation; mCallback = c; } @Override protected BubbleViewInfo doInBackground(Void... voids) { - return BubbleViewInfo.populate(mContext.get(), mStackView.get(), mIconFactory, mBubble, - mSkipInflation); + return BubbleViewInfo.populate(mContext.get(), mStackView.get(), mIconFactory, mBubble); } @Override @@ -128,36 +123,11 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask @Nullable static BubbleViewInfo populate(Context c, BubbleStackView stackView, - BubbleIconFactory iconFactory, Bubble b, boolean skipInflation) { - final NotificationEntry entry = b.getEntry(); - if (entry == null) { - // populate from ShortcutInfo when NotificationEntry is not available - final ShortcutInfo s = b.getShortcutInfo(); - return populate(c, stackView, iconFactory, skipInflation || b.isInflated(), - s.getPackage(), s.getUserHandle(), s, null); - } - final StatusBarNotification sbn = entry.getSbn(); - final String bubbleShortcutId = entry.getBubbleMetadata().getShortcutId(); - final ShortcutInfo si = bubbleShortcutId == null - ? null : entry.getRanking().getShortcutInfo(); - return populate( - c, stackView, iconFactory, skipInflation || b.isInflated(), - sbn.getPackageName(), sbn.getUser(), si, entry); - } - - private static BubbleViewInfo populate( - @NonNull final Context c, - @NonNull final BubbleStackView stackView, - @NonNull final BubbleIconFactory iconFactory, - final boolean isInflated, - @NonNull final String packageName, - @NonNull final UserHandle user, - @Nullable final ShortcutInfo shortcutInfo, - @Nullable final NotificationEntry entry) { + BubbleIconFactory iconFactory, Bubble b) { BubbleViewInfo info = new BubbleViewInfo(); // View inflation: only should do this once per bubble - if (!isInflated) { + if (!b.isInflated()) { LayoutInflater inflater = LayoutInflater.from(c); info.imageView = (BadgedImageView) inflater.inflate( R.layout.bubble_view, stackView, false /* attachToRoot */); @@ -167,8 +137,12 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask info.expandedView.setStackView(stackView); } - if (shortcutInfo != null) { - info.shortcutInfo = shortcutInfo; + StatusBarNotification sbn = b.getEntry().getSbn(); + String packageName = sbn.getPackageName(); + + String bubbleShortcutId = b.getEntry().getBubbleMetadata().getShortcutId(); + if (bubbleShortcutId != null) { + info.shortcutInfo = b.getEntry().getRanking().getShortcutInfo(); } // App name & app icon @@ -187,7 +161,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask info.appName = String.valueOf(pm.getApplicationLabel(appInfo)); } appIcon = pm.getApplicationIcon(packageName); - badgedIcon = pm.getUserBadgedIcon(appIcon, user); + badgedIcon = pm.getUserBadgedIcon(appIcon, sbn.getUser()); } catch (PackageManager.NameNotFoundException exception) { // If we can't find package... don't think we should show the bubble. Log.w(TAG, "Unable to find package: " + packageName); @@ -196,7 +170,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask // Badged bubble image Drawable bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo, - entry == null ? null : entry.getBubbleMetadata()); + b.getEntry().getBubbleMetadata()); if (bubbleDrawable == null) { // Default to app icon bubbleDrawable = appIcon; @@ -222,9 +196,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask Color.WHITE, WHITE_SCRIM_ALPHA); // Flyout - if (entry != null) { - info.flyoutMessage = extractFlyoutMessage(c, entry); - } + info.flyoutMessage = extractFlyoutMessage(c, b.getEntry()); return info; } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt index 175ed061c714..00a406e4dbc0 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt @@ -48,6 +48,8 @@ class AllModel( private var modified = false + override val moveHelper = null + override val favorites: List<ControlInfo> get() = favoriteIds.mapNotNull { id -> val control = controls.firstOrNull { it.control.controlId == id }?.control diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt index 4b283d607bb8..2f917107cc23 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt @@ -18,6 +18,7 @@ package com.android.systemui.controls.management import android.content.ComponentName import android.graphics.Rect +import android.os.Bundle import android.service.controls.Control import android.service.controls.DeviceTypes import android.view.LayoutInflater @@ -78,7 +79,7 @@ class ControlAdapter( background = parent.context.getDrawable( R.drawable.control_background_ripple) }, - model is FavoritesModel // Indicates that position information is needed + model?.moveHelper // Indicates that position information is needed ) { id, favorite -> model?.changeFavoriteStatus(id, favorite) } @@ -176,12 +177,14 @@ private class ZoneHolder(view: View) : Holder(view) { /** * Holder for using with [ControlStatusWrapper] to display names of zones. + * @param moveHelper a helper interface to facilitate a11y rearranging. Null indicates no + * rearranging * @param favoriteCallback this callback will be called whenever the favorite state of the * [Control] this view represents changes. */ internal class ControlHolder( view: View, - val withPosition: Boolean, + val moveHelper: ControlsModel.MoveHelper?, val favoriteCallback: ModelFavoriteChanger ) : Holder(view) { private val favoriteStateDescription = @@ -197,7 +200,11 @@ internal class ControlHolder( visibility = View.VISIBLE } - private val accessibilityDelegate = ControlHolderAccessibilityDelegate(this::stateDescription) + private val accessibilityDelegate = ControlHolderAccessibilityDelegate( + this::stateDescription, + this::getLayoutPosition, + moveHelper + ) init { ViewCompat.setAccessibilityDelegate(itemView, accessibilityDelegate) @@ -207,7 +214,7 @@ internal class ControlHolder( private fun stateDescription(favorite: Boolean): CharSequence? { if (!favorite) { return notFavoriteStateDescription - } else if (!withPosition) { + } else if (moveHelper == null) { return favoriteStateDescription } else { val position = layoutPosition + 1 @@ -256,15 +263,67 @@ internal class ControlHolder( } } +/** + * Accessibility delegate for [ControlHolder]. + * + * Provides the following functionality: + * * Sets the state description indicating whether the controls is Favorited or Unfavorited + * * Adds the position to the state description if necessary. + * * Adds context action for moving (rearranging) a control. + * + * @param stateRetriever function to determine the state description based on the favorite state + * @param positionRetriever function to obtain the position of this control. It only has to be + * correct in controls that are currently favorites (and therefore can + * be moved). + * @param moveHelper helper interface to determine if a control can be moved and actually move it. + */ private class ControlHolderAccessibilityDelegate( - val stateRetriever: (Boolean) -> CharSequence? + val stateRetriever: (Boolean) -> CharSequence?, + val positionRetriever: () -> Int, + val moveHelper: ControlsModel.MoveHelper? ) : AccessibilityDelegateCompat() { var isFavorite = false + companion object { + private val MOVE_BEFORE_ID = R.id.accessibility_action_controls_move_before + private val MOVE_AFTER_ID = R.id.accessibility_action_controls_move_after + } + override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) { super.onInitializeAccessibilityNodeInfo(host, info) + info.isContextClickable = false + addClickAction(host, info) + maybeAddMoveBeforeAction(host, info) + maybeAddMoveAfterAction(host, info) + + // Determine the stateDescription based on the holder information + info.stateDescription = stateRetriever(isFavorite) + // Remove the information at the end indicating row and column. + info.setCollectionItemInfo(null) + + info.className = Switch::class.java.name + } + + override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean { + if (super.performAccessibilityAction(host, action, args)) { + return true + } + return when (action) { + MOVE_BEFORE_ID -> { + moveHelper?.moveBefore(positionRetriever()) + true + } + MOVE_AFTER_ID -> { + moveHelper?.moveAfter(positionRetriever()) + true + } + else -> false + } + } + + private fun addClickAction(host: View, info: AccessibilityNodeInfoCompat) { // Change the text for the double-tap action val clickActionString = if (isFavorite) { host.context.getString(R.string.accessibility_control_change_unfavorite) @@ -276,13 +335,30 @@ private class ControlHolderAccessibilityDelegate( // “favorite/unfavorite” clickActionString) info.addAction(click) + } - // Determine the stateDescription based on the holder information - info.stateDescription = stateRetriever(isFavorite) - // Remove the information at the end indicating row and column. - info.setCollectionItemInfo(null) + private fun maybeAddMoveBeforeAction(host: View, info: AccessibilityNodeInfoCompat) { + if (moveHelper?.canMoveBefore(positionRetriever()) ?: false) { + val newPosition = positionRetriever() + 1 - 1 + val moveBefore = AccessibilityNodeInfoCompat.AccessibilityActionCompat( + MOVE_BEFORE_ID, + host.context.getString(R.string.accessibility_control_move, newPosition) + ) + info.addAction(moveBefore) + info.isContextClickable = true + } + } - info.className = Switch::class.java.name + private fun maybeAddMoveAfterAction(host: View, info: AccessibilityNodeInfoCompat) { + if (moveHelper?.canMoveAfter(positionRetriever()) ?: false) { + val newPosition = positionRetriever() + 1 + 1 + val moveAfter = AccessibilityNodeInfoCompat.AccessibilityActionCompat( + MOVE_AFTER_ID, + host.context.getString(R.string.accessibility_control_move, newPosition) + ) + info.addAction(moveAfter) + info.isContextClickable = true + } } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt index 37b6d15c0afe..254395368bf9 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt @@ -42,6 +42,8 @@ interface ControlsModel { */ val elements: List<ElementWrapper> + val moveHelper: MoveHelper? + /** * Change the favorite status of a particular control. */ @@ -69,6 +71,34 @@ interface ControlsModel { */ fun onFirstChange() } + + /** + * Interface to facilitate moving controls from an [AccessibilityDelegate]. + * + * All positions should be 0 based. + */ + interface MoveHelper { + + /** + * Whether the control in `position` can be moved to the position before it. + */ + fun canMoveBefore(position: Int): Boolean + + /** + * Whether the control in `position` can be moved to the position after it. + */ + fun canMoveAfter(position: Int): Boolean + + /** + * Move the control in `position` to the position before it. + */ + fun moveBefore(position: Int) + + /** + * Move the control in `position` to the position after it. + */ + fun moveAfter(position: Int) + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt index 411170cb322c..524250134e9b 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.controls.management import android.content.ComponentName +import android.util.Log import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import com.android.systemui.controls.ControlInterface @@ -39,9 +40,39 @@ class FavoritesModel( private val favoritesModelCallback: FavoritesModelCallback ) : ControlsModel { + companion object { + private const val TAG = "FavoritesModel" + } + private var adapter: RecyclerView.Adapter<*>? = null private var modified = false + override val moveHelper = object : ControlsModel.MoveHelper { + override fun canMoveBefore(position: Int): Boolean { + return position > 0 && position < dividerPosition + } + + override fun canMoveAfter(position: Int): Boolean { + return position >= 0 && position < dividerPosition - 1 + } + + override fun moveBefore(position: Int) { + if (!canMoveBefore(position)) { + Log.w(TAG, "Cannot move position $position before") + } else { + onMoveItem(position, position - 1) + } + } + + override fun moveAfter(position: Int) { + if (!canMoveAfter(position)) { + Log.w(TAG, "Cannot move position $position after") + } else { + onMoveItem(position, position + 1) + } + } + } + override fun attachAdapter(adapter: RecyclerView.Adapter<*>) { this.adapter = adapter } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 9cfb1b27cbf3..839ea69953af 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -56,6 +56,7 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; import android.provider.DeviceConfig; +import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Log; import android.util.MathUtils; @@ -160,6 +161,9 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification"; static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip"; + // From WizardManagerHelper.java + private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete"; + private static final String TAG = "GlobalScreenshot"; private static final long SCREENSHOT_FLASH_IN_DURATION_MS = 133; @@ -460,6 +464,13 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset return; } + if (!isUserSetupComplete()) { + // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing + // and sharing shouldn't be exposed to the user. + saveScreenshotAndToast(finisher); + return; + } + // Optimizations mScreenBitmap.setHasAlpha(false); mScreenBitmap.prepareToDraw(); @@ -546,6 +557,41 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } /** + * Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on + * failure). + */ + private void saveScreenshotAndToast(Consumer<Uri> finisher) { + // Play the shutter sound to notify that we've taken a screenshot + mScreenshotHandler.post(() -> { + mCameraSound.play(MediaActionSound.SHUTTER_CLICK); + }); + + saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() { + @Override + void onActionsReady(SavedImageData imageData) { + finisher.accept(imageData.uri); + if (imageData.uri == null) { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED); + mNotificationsController.notifyScreenshotError( + R.string.screenshot_failed_to_capture_text); + } else { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED); + + mScreenshotHandler.post(() -> { + Toast.makeText(mContext, R.string.screenshot_saved_title, + Toast.LENGTH_SHORT).show(); + }); + } + } + }); + } + + private boolean isUserSetupComplete() { + return Settings.Secure.getInt(mContext.getContentResolver(), + SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1; + } + + /** * Clears current screenshot */ private void dismissScreenshot(String reason, boolean immediate) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt new file mode 100644 index 000000000000..7f7ff9cf4881 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt @@ -0,0 +1,88 @@ +/* + * 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.statusbar + +import android.app.PendingIntent +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.dagger.NotifInteractionLog +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import javax.inject.Inject + +/** + * Logger class for events related to the user clicking on notification actions + */ +class ActionClickLogger @Inject constructor( + @NotifInteractionLog private val buffer: LogBuffer +) { + fun logInitialClick( + entry: NotificationEntry?, + pendingIntent: PendingIntent + ) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = entry?.key + str2 = entry?.ranking?.channel?.id + str3 = pendingIntent.intent.toString() + }, { + "ACTION CLICK $str1 (channel=$str2) for pending intent $str3" + }) + } + + fun logRemoteInputWasHandled( + entry: NotificationEntry? + ) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = entry?.key + }, { + " [Action click] Triggered remote input (for $str1))" + }) + } + + fun logStartingIntentWithDefaultHandler( + entry: NotificationEntry?, + pendingIntent: PendingIntent + ) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = entry?.key + str2 = pendingIntent.intent.toString() + }, { + " [Action click] Launching intent $str2 via default handler (for $str1)" + }) + } + + fun logWaitingToCloseKeyguard( + pendingIntent: PendingIntent + ) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = pendingIntent.intent.toString() + }, { + " [Action click] Intent $str1 launches an activity, dismissing keyguard first..." + }) + } + + fun logKeyguardGone( + pendingIntent: PendingIntent + ) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = pendingIntent.intent.toString() + }, { + " [Action click] Keyguard dismissed, calling default handler for intent $str1" + }) + } +} + +private const val TAG = "ActionClickLogger"
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index bf28040233f5..9181c69e3722 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -54,7 +54,7 @@ import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.dagger.StatusBarModule; +import com.android.systemui.statusbar.dagger.StatusBarDependenciesModule; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -114,6 +114,7 @@ public class NotificationRemoteInputManager implements Dumpable { private final SmartReplyController mSmartReplyController; private final NotificationEntryManager mEntryManager; private final Handler mMainHandler; + private final ActionClickLogger mLogger; private final Lazy<StatusBar> mStatusBarLazy; @@ -138,14 +139,18 @@ public class NotificationRemoteInputManager implements Dumpable { mStatusBarLazy.get().wakeUpIfDozing(SystemClock.uptimeMillis(), view, "NOTIFICATION_CLICK"); + final NotificationEntry entry = getNotificationForParent(view.getParent()); + mLogger.logInitialClick(entry, pendingIntent); + if (handleRemoteInput(view, pendingIntent)) { + mLogger.logRemoteInputWasHandled(entry); return true; } if (DEBUG) { Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent); } - logActionClick(view, pendingIntent); + logActionClick(view, entry, pendingIntent); // The intent we are sending is for the application, which // won't have permission to immediately start an activity after // the user switches to home. We know it is safe to do at this @@ -158,11 +163,15 @@ public class NotificationRemoteInputManager implements Dumpable { Pair<Intent, ActivityOptions> options = response.getLaunchOptions(view); options.second.setLaunchWindowingMode( WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY); + mLogger.logStartingIntentWithDefaultHandler(entry, pendingIntent); return RemoteViews.startPendingIntent(view, pendingIntent, options); }); } - private void logActionClick(View view, PendingIntent actionIntent) { + private void logActionClick( + View view, + NotificationEntry entry, + PendingIntent actionIntent) { Integer actionIndex = (Integer) view.getTag(com.android.internal.R.id.notification_action_index_tag); if (actionIndex == null) { @@ -170,7 +179,7 @@ public class NotificationRemoteInputManager implements Dumpable { return; } ViewParent parent = view.getParent(); - StatusBarNotification statusBarNotification = getNotificationForParent(parent); + StatusBarNotification statusBarNotification = entry.getSbn(); if (statusBarNotification == null) { Log.w(TAG, "Couldn't determine notification for click."); return; @@ -212,10 +221,10 @@ public class NotificationRemoteInputManager implements Dumpable { } } - private StatusBarNotification getNotificationForParent(ViewParent parent) { + private NotificationEntry getNotificationForParent(ViewParent parent) { while (parent != null) { if (parent instanceof ExpandableNotificationRow) { - return ((ExpandableNotificationRow) parent).getEntry().getSbn(); + return ((ExpandableNotificationRow) parent).getEntry(); } parent = parent.getParent(); } @@ -255,7 +264,7 @@ public class NotificationRemoteInputManager implements Dumpable { }; /** - * Injected constructor. See {@link StatusBarModule}. + * Injected constructor. See {@link StatusBarDependenciesModule}. */ public NotificationRemoteInputManager( Context context, @@ -265,13 +274,15 @@ public class NotificationRemoteInputManager implements Dumpable { Lazy<StatusBar> statusBarLazy, StatusBarStateController statusBarStateController, @Main Handler mainHandler, - RemoteInputUriController remoteInputUriController) { + RemoteInputUriController remoteInputUriController, + ActionClickLogger logger) { mContext = context; mLockscreenUserManager = lockscreenUserManager; mSmartReplyController = smartReplyController; mEntryManager = notificationEntryManager; mStatusBarLazy = statusBarLazy; mMainHandler = mainHandler; + mLogger = logger; mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java index f0fed13114ba..b08eb9fafe41 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java @@ -25,6 +25,7 @@ import com.android.systemui.bubbles.BubbleController; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.media.MediaDataManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.ActionClickLogger; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.MediaArtworkProcessor; import com.android.systemui.statusbar.NotificationListener; @@ -73,7 +74,8 @@ public interface StatusBarDependenciesModule { Lazy<StatusBar> statusBarLazy, StatusBarStateController statusBarStateController, Handler mainHandler, - RemoteInputUriController remoteInputUriController) { + RemoteInputUriController remoteInputUriController, + ActionClickLogger actionClickLogger) { return new NotificationRemoteInputManager( context, lockscreenUserManager, @@ -82,7 +84,8 @@ public interface StatusBarDependenciesModule { statusBarLazy, statusBarStateController, mainHandler, - remoteInputUriController); + remoteInputUriController, + actionClickLogger); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java index 57be1a502842..92426e54ec91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java @@ -19,8 +19,6 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import static android.service.notification.NotificationStats.DISMISSAL_OTHER; import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL; -import android.annotation.NonNull; - import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.statusbar.notification.collection.NotifCollection; @@ -123,7 +121,7 @@ public class BubbleCoordinator implements Coordinator { private final BubbleController.NotifCallback mNotifCallback = new BubbleController.NotifCallback() { @Override - public void removeNotification(@NonNull final NotificationEntry entry, int reason) { + public void removeNotification(NotificationEntry entry, int reason) { if (isInterceptingDismissal(entry)) { mInterceptedDismissalEntries.remove(entry.getKey()); mOnEndDismissInterception.onEndDismissInterception(mDismissInterceptor, entry, @@ -143,7 +141,7 @@ public class BubbleCoordinator implements Coordinator { } @Override - public void maybeCancelSummary(@NonNull final NotificationEntry entry) { + public void maybeCancelSummary(NotificationEntry entry) { // no-op } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java index d0a872eb112f..80785db6df3e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.phone; -import android.annotation.NonNull; import android.annotation.Nullable; import android.service.notification.StatusBarNotification; import android.util.ArraySet; @@ -455,7 +454,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State * If there is a {@link NotificationGroup} associated with the provided entry, this method * will update the suppression of that group. */ - public void updateSuppression(@NonNull final NotificationEntry entry) { + public void updateSuppression(NotificationEntry entry) { NotificationGroup group = mGroupMap.get(getGroupKey(entry.getSbn())); if (group != null) { updateSuppression(group); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java index 428de9e9adbb..669e6a4f2138 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java @@ -37,6 +37,7 @@ import android.view.ViewParent; import com.android.systemui.ActivityIntentHelper; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.ActionClickLogger; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -73,6 +74,7 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks, private View mPendingRemoteInputView; private KeyguardManager mKeyguardManager; private final CommandQueue mCommandQueue; + private final ActionClickLogger mActionClickLogger; private int mDisabled2; protected BroadcastReceiver mChallengeReceiver = new ChallengeReceiver(); private Handler mMainHandler = new Handler(); @@ -87,7 +89,8 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks, StatusBarStateController statusBarStateController, StatusBarKeyguardViewManager statusBarKeyguardViewManager, ActivityStarter activityStarter, ShadeController shadeController, - CommandQueue commandQueue) { + CommandQueue commandQueue, + ActionClickLogger clickLogger) { mContext = context; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mShadeController = shadeController; @@ -101,6 +104,7 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks, mKeyguardManager = context.getSystemService(KeyguardManager.class); mCommandQueue = commandQueue; mCommandQueue.addCallback(this); + mActionClickLogger = clickLogger; mActivityIntentHelper = new ActivityIntentHelper(mContext); mGroupManager = groupManager; // Listen to onKeyguardShowingChanged in case a managed profile needs to be unlocked @@ -304,9 +308,12 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks, NotificationRemoteInputManager.ClickHandler defaultHandler) { final boolean isActivity = pendingIntent.isActivity(); if (isActivity) { + mActionClickLogger.logWaitingToCloseKeyguard(pendingIntent); final boolean afterKeyguardGone = mActivityIntentHelper.wouldLaunchResolverActivity( pendingIntent.getIntent(), mLockscreenUserManager.getCurrentUserId()); mActivityStarter.dismissKeyguardThenExecute(() -> { + mActionClickLogger.logKeyguardGone(pendingIntent); + try { ActivityManager.getService().resumeAppSwitches(); } catch (RemoteException e) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 52923abae07c..96e868d2a618 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -317,7 +317,7 @@ public class BubbleControllerTest extends SysuiTestCase { verify(mNotificationEntryManager).updateNotifications(any()); mBubbleController.removeBubble( - mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); + mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE); assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey())); verify(mNotificationEntryManager, times(2)).updateNotifications(anyString()); @@ -329,7 +329,7 @@ public class BubbleControllerTest extends SysuiTestCase { mBubbleController.updateBubble(mRow2.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); mBubbleController.removeBubble( - mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); + mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE); Bubble b = mBubbleData.getOverflowBubbleWithKey(mRow.getEntry().getKey()); assertThat(mBubbleData.getOverflowBubbles()).isEqualTo(ImmutableList.of(b)); @@ -350,10 +350,9 @@ public class BubbleControllerTest extends SysuiTestCase { mBubbleController.updateBubble(mRow.getEntry(), /* suppressFlyout */ false, /* showInShade */ true); mBubbleController.removeBubble( - mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); + mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE); - mBubbleController.removeBubble( - mRow.getEntry().getKey(), BubbleController.DISMISS_NOTIF_CANCEL); + mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_NOTIF_CANCEL); verify(mNotificationEntryManager, times(1)).performRemoveNotification( eq(mRow.getEntry().getSbn()), anyInt()); assertThat(mBubbleData.getOverflowBubbles()).isEmpty(); @@ -366,7 +365,7 @@ public class BubbleControllerTest extends SysuiTestCase { assertTrue(mBubbleController.hasBubbles()); mBubbleController.removeBubble( - mRow.getEntry().getKey(), BubbleController.DISMISS_USER_CHANGED); + mRow.getEntry(), BubbleController.DISMISS_USER_CHANGED); verify(mNotificationEntryManager, never()).performRemoveNotification( eq(mRow.getEntry().getSbn()), anyInt()); assertFalse(mBubbleController.hasBubbles()); @@ -564,8 +563,7 @@ public class BubbleControllerTest extends SysuiTestCase { // Dismiss currently expanded mBubbleController.removeBubble( - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()) - .getEntry().getKey(), + mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry(), BubbleController.DISMISS_USER_GESTURE); verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey()); @@ -576,8 +574,7 @@ public class BubbleControllerTest extends SysuiTestCase { // Dismiss that one mBubbleController.removeBubble( - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()) - .getEntry().getKey(), + mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry(), BubbleController.DISMISS_USER_GESTURE); // Make sure state changes and collapse happens @@ -703,7 +700,7 @@ public class BubbleControllerTest extends SysuiTestCase { @Test public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException { mBubbleController.updateBubble(mRow.getEntry()); - mBubbleController.removeBubble(mRow.getEntry().getKey(), BubbleController.DISMISS_AGED); + mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_AGED); verify(mDeleteIntent, never()).send(); } @@ -711,7 +708,7 @@ public class BubbleControllerTest extends SysuiTestCase { public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException { mBubbleController.updateBubble(mRow.getEntry()); mBubbleController.removeBubble( - mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); + mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE); verify(mDeleteIntent, times(1)).send(); } @@ -811,7 +808,7 @@ public class BubbleControllerTest extends SysuiTestCase { // Dismiss the bubble into overflow. mBubbleController.removeBubble( - mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); + mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE); assertFalse(mBubbleController.hasBubbles()); boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested( @@ -832,7 +829,7 @@ public class BubbleControllerTest extends SysuiTestCase { mRow.getEntry())); mBubbleController.removeBubble( - mRow.getEntry().getKey(), BubbleController.DISMISS_NO_LONGER_BUBBLE); + mRow.getEntry(), BubbleController.DISMISS_NO_LONGER_BUBBLE); assertFalse(mBubbleController.hasBubbles()); boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested( @@ -854,12 +851,12 @@ public class BubbleControllerTest extends SysuiTestCase { mBubbleData.setMaxOverflowBubbles(1); mBubbleController.removeBubble( - mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); + mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE); assertEquals(mBubbleData.getBubbles().size(), 2); assertEquals(mBubbleData.getOverflowBubbles().size(), 1); mBubbleController.removeBubble( - mRow2.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); + mRow2.getEntry(), BubbleController.DISMISS_USER_GESTURE); // Overflow max of 1 is reached; mRow is oldest, so it gets removed verify(mNotificationEntryManager, times(1)).performRemoveNotification( mRow.getEntry().getSbn(), REASON_CANCEL); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java index 882504bdb5e4..66f119a082a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java @@ -20,8 +20,11 @@ import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanki import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; @@ -177,8 +180,7 @@ public class BubbleDataTest extends SysuiTestCase { mBubbleData.setListener(mListener); // Test - mBubbleData.notificationEntryRemoved( - mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE); + mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE); // Verify verifyUpdateReceived(); @@ -295,14 +297,12 @@ public class BubbleDataTest extends SysuiTestCase { mBubbleData.setListener(mListener); mBubbleData.setMaxOverflowBubbles(1); - mBubbleData.notificationEntryRemoved( - mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE); + mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE); verifyUpdateReceived(); assertOverflowChangedTo(ImmutableList.of(mBubbleA1)); // Overflow max of 1 is reached; A1 is oldest, so it gets removed - mBubbleData.notificationEntryRemoved( - mEntryA2.getKey(), BubbleController.DISMISS_USER_GESTURE); + mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE); verifyUpdateReceived(); assertOverflowChangedTo(ImmutableList.of(mBubbleA2)); } @@ -449,8 +449,7 @@ public class BubbleDataTest extends SysuiTestCase { mBubbleData.setListener(mListener); // Test - mBubbleData.notificationEntryRemoved( - mEntryA2.getKey(), BubbleController.DISMISS_USER_GESTURE); + mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE); verifyUpdateReceived(); assertOrderChangedTo(mBubbleB2, mBubbleB1, mBubbleA1); } @@ -470,8 +469,7 @@ public class BubbleDataTest extends SysuiTestCase { mBubbleData.setListener(mListener); // Test - mBubbleData.notificationEntryRemoved( - mEntryB1.getKey(), BubbleController.DISMISS_USER_GESTURE); + mBubbleData.notificationEntryRemoved(mEntryB1, BubbleController.DISMISS_USER_GESTURE); verifyUpdateReceived(); assertOrderNotChanged(); } @@ -491,8 +489,7 @@ public class BubbleDataTest extends SysuiTestCase { mBubbleData.setListener(mListener); // Test - mBubbleData.notificationEntryRemoved( - mEntryA1.getKey(), BubbleController.DISMISS_NOTIF_CANCEL); + mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_NOTIF_CANCEL); verifyUpdateReceived(); assertOrderChangedTo(mBubbleB2, mBubbleB1, mBubbleA2); } @@ -513,14 +510,12 @@ public class BubbleDataTest extends SysuiTestCase { mBubbleData.setListener(mListener); // Test - mBubbleData.notificationEntryRemoved( - mEntryA1.getKey(), BubbleController.DISMISS_NOTIF_CANCEL); + mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_NOTIF_CANCEL); verifyUpdateReceived(); assertOverflowChangedTo(ImmutableList.of(mBubbleA2)); // Test - mBubbleData.notificationEntryRemoved( - mEntryA2.getKey(), BubbleController.DISMISS_GROUP_CANCELLED); + mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_GROUP_CANCELLED); verifyUpdateReceived(); assertOverflowChangedTo(ImmutableList.of()); } @@ -539,8 +534,7 @@ public class BubbleDataTest extends SysuiTestCase { mBubbleData.setListener(mListener); // Test - mBubbleData.notificationEntryRemoved( - mEntryA2.getKey(), BubbleController.DISMISS_NOTIF_CANCEL); + mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_NOTIF_CANCEL); verifyUpdateReceived(); assertSelectionChangedTo(mBubbleB2); } @@ -631,8 +625,7 @@ public class BubbleDataTest extends SysuiTestCase { mBubbleData.setListener(mListener); // Test - mBubbleData.notificationEntryRemoved( - mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE); + mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE); // Verify the selection was cleared. verifyUpdateReceived(); @@ -786,8 +779,7 @@ public class BubbleDataTest extends SysuiTestCase { mBubbleData.setListener(mListener); // Test - mBubbleData.notificationEntryRemoved( - mEntryB2.getKey(), BubbleController.DISMISS_USER_GESTURE); + mBubbleData.notificationEntryRemoved(mEntryB2, BubbleController.DISMISS_USER_GESTURE); verifyUpdateReceived(); assertOrderChangedTo(mBubbleB1, mBubbleA2, mBubbleA1); } @@ -812,13 +804,11 @@ public class BubbleDataTest extends SysuiTestCase { mBubbleData.setListener(mListener); // Test - mBubbleData.notificationEntryRemoved( - mEntryA2.getKey(), BubbleController.DISMISS_USER_GESTURE); + mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE); verifyUpdateReceived(); assertSelectionChangedTo(mBubbleA1); - mBubbleData.notificationEntryRemoved( - mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE); + mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE); verifyUpdateReceived(); assertSelectionChangedTo(mBubbleB1); } @@ -933,8 +923,7 @@ public class BubbleDataTest extends SysuiTestCase { mBubbleData.setListener(mListener); // Test - mBubbleData.notificationEntryRemoved( - mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE); + mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE); verifyUpdateReceived(); assertExpandedChangedTo(false); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java index c16801cdf8ad..73b876019863 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java @@ -285,8 +285,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { assertTrue(mBubbleController.hasBubbles()); verify(mNotifCallback, times(1)).invalidateNotifications(anyString()); - mBubbleController.removeBubble( - mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); + mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE); assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey())); verify(mNotifCallback, times(2)).invalidateNotifications(anyString()); } @@ -303,8 +302,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).setSuppressNotification(true); // Now remove the bubble - mBubbleController.removeBubble( - mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); + mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE); assertTrue(mBubbleData.hasOverflowBubbleWithKey(mRow.getEntry().getKey())); // We don't remove the notification since the bubble is still in overflow. @@ -324,8 +322,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).setSuppressNotification(true); // Now remove the bubble - mBubbleController.removeBubble( - mRow.getEntry().getKey(), BubbleController.DISMISS_NOTIF_CANCEL); + mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_NOTIF_CANCEL); assertFalse(mBubbleData.hasOverflowBubbleWithKey(mRow.getEntry().getKey())); // Since the notif is dismissed and not in overflow, once the bubble is removed, @@ -505,8 +502,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { // Dismiss currently expanded mBubbleController.removeBubble( - mBubbleData.getBubbleInStackWithKey( - stackView.getExpandedBubble().getKey()).getEntry().getKey(), + mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry(), BubbleController.DISMISS_USER_GESTURE); verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey()); @@ -517,8 +513,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { // Dismiss that one mBubbleController.removeBubble( - mBubbleData.getBubbleInStackWithKey( - stackView.getExpandedBubble().getKey()).getEntry().getKey(), + mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry(), BubbleController.DISMISS_USER_GESTURE); // Make sure state changes and collapse happens @@ -618,7 +613,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { @Test public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException { mBubbleController.updateBubble(mRow.getEntry()); - mBubbleController.removeBubble(mRow.getEntry().getKey(), BubbleController.DISMISS_AGED); + mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_AGED); verify(mDeleteIntent, never()).send(); } @@ -626,7 +621,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException { mBubbleController.updateBubble(mRow.getEntry()); mBubbleController.removeBubble( - mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); + mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE); verify(mDeleteIntent, times(1)).send(); } @@ -691,7 +686,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { // Dismiss the bubble mBubbleController.removeBubble( - mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); + mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE); assertFalse(mBubbleController.hasBubbles()); // Dismiss the notification @@ -712,7 +707,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { // Dismiss the bubble mBubbleController.removeBubble( - mRow.getEntry().getKey(), BubbleController.DISMISS_NOTIF_CANCEL); + mRow.getEntry(), BubbleController.DISMISS_NOTIF_CANCEL); assertFalse(mBubbleController.hasBubbles()); // Dismiss the notification diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java index 1117646c482b..5a7dea4de29c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java @@ -82,7 +82,8 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { () -> mock(StatusBar.class), mStateController, Handler.createAsync(Looper.myLooper()), - mRemoteInputUriController); + mRemoteInputUriController, + mock(ActionClickLogger.class)); mEntry = new NotificationEntryBuilder() .setPkg(TEST_PACKAGE_NAME) .setOpPkg(TEST_PACKAGE_NAME) @@ -256,17 +257,26 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { private class TestableNotificationRemoteInputManager extends NotificationRemoteInputManager { - TestableNotificationRemoteInputManager(Context context, + TestableNotificationRemoteInputManager( + Context context, NotificationLockscreenUserManager lockscreenUserManager, SmartReplyController smartReplyController, NotificationEntryManager notificationEntryManager, Lazy<StatusBar> statusBarLazy, StatusBarStateController statusBarStateController, Handler mainHandler, - RemoteInputUriController remoteInputUriController) { - super(context, lockscreenUserManager, smartReplyController, notificationEntryManager, - statusBarLazy, statusBarStateController, mainHandler, - remoteInputUriController); + RemoteInputUriController remoteInputUriController, + ActionClickLogger actionClickLogger) { + super( + context, + lockscreenUserManager, + smartReplyController, + notificationEntryManager, + statusBarLazy, + statusBarStateController, + mainHandler, + remoteInputUriController, + actionClickLogger); } public void setUpWithPresenterForTest(Callback callback, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java index 22dc0803339c..79507e96ae02 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java @@ -92,7 +92,8 @@ public class SmartReplyControllerTest extends SysuiTestCase { mNotificationEntryManager, () -> mock(StatusBar.class), mStatusBarStateController, Handler.createAsync(Looper.myLooper()), - mRemoteInputUriController); + mRemoteInputUriController, + mock(ActionClickLogger.class)); mRemoteInputManager.setUpWithCallback(mCallback, mDelegate); mNotification = new Notification.Builder(mContext, "") .setSmallIcon(R.drawable.ic_person) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java index cd2c3498ce56..bf2bd38638ff 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java @@ -31,6 +31,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.statusbar.ActionClickLogger; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.SysuiStatusBarStateController; @@ -73,7 +74,8 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { mRemoteInputCallback = spy(new StatusBarRemoteInputCallback(mContext, mock(NotificationGroupManager.class), mNotificationLockscreenUserManager, mKeyguardStateController, mStatusBarStateController, mStatusBarKeyguardViewManager, - mActivityStarter, mShadeController, new CommandQueue(mContext))); + mActivityStarter, mShadeController, new CommandQueue(mContext), + mock(ActionClickLogger.class))); mRemoteInputCallback.mChallengeReceiver = mRemoteInputCallback.new ChallengeReceiver(); } diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 4f5a02ad22fa..2a65b33461cf 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -201,7 +201,6 @@ final class LocalDisplayAdapter extends DisplayAdapter { private SurfaceControl.DisplayConfig[] mDisplayConfigs; private Spline mSystemBrightnessToNits; private Spline mNitsToHalBrightness; - private boolean mHalBrightnessSupport; private DisplayDeviceConfig mDisplayDeviceConfig; @@ -225,7 +224,6 @@ final class LocalDisplayAdapter extends DisplayAdapter { } mAllmSupported = SurfaceControl.getAutoLowLatencyModeSupport(displayToken); mGameContentTypeSupported = SurfaceControl.getGameContentTypeSupport(displayToken); - mHalBrightnessSupport = SurfaceControl.getDisplayBrightnessSupport(displayToken); mDisplayDeviceConfig = null; // Defer configuration file loading BackgroundThread.getHandler().sendMessage(PooledLambda.obtainMessage( @@ -717,11 +715,10 @@ final class LocalDisplayAdapter extends DisplayAdapter { Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayBrightness(" + "id=" + physicalDisplayId + ", brightness=" + brightness + ")"); try { - // TODO: make it float if (isHalBrightnessRangeSpecified()) { brightness = displayBrightnessToHalBrightness( - BrightnessSynchronizer.brightnessFloatToInt(getContext(), - brightness)); + BrightnessSynchronizer.brightnessFloatToIntRange( + getContext(), brightness)); } if (mBacklight != null) { mBacklight.setBrightness(brightness); @@ -744,12 +741,13 @@ final class LocalDisplayAdapter extends DisplayAdapter { * Hal brightness space if the HAL brightness space has been provided via * a display device configuration file. */ - private float displayBrightnessToHalBrightness(int brightness) { + private float displayBrightnessToHalBrightness(float brightness) { if (!isHalBrightnessRangeSpecified()) { return PowerManager.BRIGHTNESS_INVALID_FLOAT; } - if (brightness == 0) { + if (BrightnessSynchronizer.floatEquals( + brightness, PowerManager.BRIGHTNESS_OFF)) { return PowerManager.BRIGHTNESS_OFF_FLOAT; } diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 12309f407786..a859a42d8a8f 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -289,6 +289,7 @@ public class StatsPullAtomService extends SystemService { private StatsPullAtomCallbackImpl mStatsCallbackImpl; private int mAppOpsSamplingRate = 0; + private final ArraySet<Integer> mDangerousAppOpsList = new ArraySet<>(); // Baselines that stores list of NetworkStats right after initializing, with associated // information. This is used to calculate difference when pulling @@ -526,6 +527,25 @@ public class StatsPullAtomService extends SystemService { } catch (RemoteException e) { Slog.e(TAG, "failed to initialize healthHalWrapper"); } + + // Initialize list of AppOps related to DangerousPermissions + PackageManager pm = mContext.getPackageManager(); + for (int op = 0; op < AppOpsManager._NUM_OP; op++) { + String perm = AppOpsManager.opToPermission(op); + if (perm == null) { + continue; + } else { + PermissionInfo permInfo; + try { + permInfo = pm.getPermissionInfo(perm, 0); + if (permInfo.getProtection() == PROTECTION_DANGEROUS) { + mDangerousAppOpsList.add(op); + } + } catch (PackageManager.NameNotFoundException exception) { + continue; + } + } + } } void registerEventListeners() { @@ -3113,22 +3133,8 @@ public class StatsPullAtomService extends SystemService { e.writeLong(op.getBackgroundRejectCount(OP_FLAGS_PULLED)); e.writeLong(op.getForegroundAccessDuration(OP_FLAGS_PULLED)); e.writeLong(op.getBackgroundAccessDuration(OP_FLAGS_PULLED)); + e.writeBoolean(mDangerousAppOpsList.contains(op.getOpCode())); - String perm = AppOpsManager.opToPermission(op.getOpCode()); - if (perm == null) { - e.writeBoolean(false); - } else { - PermissionInfo permInfo; - try { - permInfo = mContext.getPackageManager().getPermissionInfo( - perm, - 0); - e.writeBoolean( - permInfo.getProtection() == PROTECTION_DANGEROUS); - } catch (PackageManager.NameNotFoundException exception) { - e.writeBoolean(false); - } - } if (atomTag == FrameworkStatsLog.ATTRIBUTED_APP_OPS) { e.writeInt(mAppOpsSamplingRate); } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 91c849c4110f..0598680f8c5d 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -6684,6 +6684,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } @Override + void getAnimationPosition(Point outPosition) { + // Always animate from zero because if the activity doesn't fill the task, the letterbox + // will fill the remaining area that should be included in the animation. + outPosition.set(0, 0); + } + + @Override public void onConfigurationChanged(Configuration newParentConfig) { if (mCompatDisplayInsets != null) { Configuration overrideConfig = getRequestedOverrideConfiguration(); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 5d7ec127da46..7bfddd79f8a4 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2120,6 +2120,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return getBounds(); } + /** Gets the position relative to parent for animation. */ + void getAnimationPosition(Point outPosition) { + getRelativePosition(outPosition); + } + /** * Applies the app transition animation according the given the layout properties in the * window hierarchy. @@ -2178,9 +2183,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // Separate position and size for use in animators. mTmpRect.set(getAnimationBounds(appStackClipMode)); - if (sHierarchicalAnimations) { - getRelativePosition(mTmpPoint); - } else { + getAnimationPosition(mTmpPoint); + if (!sHierarchicalAnimations) { + // Non-hierarchical animation uses position in global coordinates. mTmpPoint.set(mTmpRect.left, mTmpRect.top); } mTmpRect.offsetTo(0, 0); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 0815ee523555..18c25c142113 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -247,6 +247,7 @@ import android.stats.devicepolicy.DevicePolicyEnums; import android.telephony.TelephonyManager; import android.telephony.data.ApnSetting; import android.text.TextUtils; +import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; @@ -16005,31 +16006,27 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private @PersonalAppsSuspensionReason int updatePersonalAppsSuspension( int profileUserId, boolean unlocked) { final boolean suspendedExplicitly; - final int deadlineState; - final String poPackage; + final boolean suspendedByTimeout; synchronized (getLockObject()) { final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(profileUserId); if (profileOwner != null) { - deadlineState = + final int deadlineState = updateProfileOffDeadlineLocked(profileUserId, profileOwner, unlocked); suspendedExplicitly = profileOwner.mSuspendPersonalApps; - poPackage = profileOwner.info.getPackageName(); + suspendedByTimeout = deadlineState == PROFILE_OFF_DEADLINE_REACHED; + Slog.d(LOG_TAG, String.format( + "Personal apps suspended explicitly: %b, deadline state: %d", + suspendedExplicitly, deadlineState)); + final int notificationState = + unlocked ? PROFILE_OFF_DEADLINE_DEFAULT : deadlineState; + updateProfileOffDeadlineNotificationLocked( + profileUserId, profileOwner, notificationState); } else { - poPackage = null; suspendedExplicitly = false; - deadlineState = PROFILE_OFF_DEADLINE_DEFAULT; + suspendedByTimeout = false; } } - Slog.d(LOG_TAG, String.format("Personal apps suspended explicitly: %b, deadline state: %d", - suspendedExplicitly, deadlineState)); - - if (poPackage != null) { - final int notificationState = unlocked ? PROFILE_OFF_DEADLINE_DEFAULT : deadlineState; - updateProfileOffDeadlineNotification(profileUserId, poPackage, notificationState); - } - - final boolean suspendedByTimeout = deadlineState == PROFILE_OFF_DEADLINE_REACHED; final int parentUserId = getProfileParentId(profileUserId); suspendPersonalAppsInternal(parentUserId, suspendedExplicitly || suspendedByTimeout); @@ -16141,9 +16138,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - private void updateProfileOffDeadlineNotification( - int profileUserId, String profileOwnerPackage, int notificationState) { - + @GuardedBy("getLockObject()") + private void updateProfileOffDeadlineNotificationLocked( + int profileUserId, ActiveAdmin profileOwner, int notificationState) { if (notificationState == PROFILE_OFF_DEADLINE_DEFAULT) { mInjector.getNotificationManager().cancel(SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED); return; @@ -16161,11 +16158,23 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final Notification.Action turnProfileOnButton = new Notification.Action.Builder(null /* icon */, buttonText, pendingIntent).build(); - final String text = mContext.getString( - notificationState == PROFILE_OFF_DEADLINE_WARNING - ? R.string.personal_apps_suspension_tomorrow_text - : R.string.personal_apps_suspension_text); - final boolean ongoing = notificationState == PROFILE_OFF_DEADLINE_REACHED; + final String text; + final boolean ongoing; + if (notificationState == PROFILE_OFF_DEADLINE_WARNING) { + // Round to the closest integer number of days. + final int maxDays = (int) + ((profileOwner.mProfileMaximumTimeOffMillis + MS_PER_DAY / 2) / MS_PER_DAY); + final String date = DateUtils.formatDateTime( + mContext, profileOwner.mProfileOffDeadline, DateUtils.FORMAT_SHOW_DATE); + final String time = DateUtils.formatDateTime( + mContext, profileOwner.mProfileOffDeadline, DateUtils.FORMAT_SHOW_TIME); + text = mContext.getString( + R.string.personal_apps_suspension_soon_text, date, time, maxDays); + ongoing = false; + } else { + text = mContext.getString(R.string.personal_apps_suspension_text); + ongoing = true; + } final int color = mContext.getColor(R.color.personal_apps_suspension_notification_color); final Bundle extras = new Bundle(); // TODO: Create a separate string for this. diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 17dd2868f4bc..724048b1b8ee 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -204,7 +204,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { // Notification title and text for setManagedProfileMaximumTimeOff tests: private static final String PROFILE_OFF_SUSPENSION_TITLE = "suspension_title"; private static final String PROFILE_OFF_SUSPENSION_TEXT = "suspension_text"; - private static final String PROFILE_OFF_SUSPENSION_TOMORROW_TEXT = "suspension_tomorrow_text"; + private static final String PROFILE_OFF_SUSPENSION_SOON_TEXT = "suspension_tomorrow_text"; @Override protected void setUp() throws Exception { @@ -6331,7 +6331,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { // Now the user should see a warning notification. verify(getServices().notificationManager, times(1)) .notify(anyInt(), argThat(hasExtra(EXTRA_TITLE, PROFILE_OFF_SUSPENSION_TITLE, - EXTRA_TEXT, PROFILE_OFF_SUSPENSION_TOMORROW_TEXT))); + EXTRA_TEXT, PROFILE_OFF_SUSPENSION_SOON_TEXT))); // Apps shouldn't be suspended yet. verifyZeroInteractions(getServices().ipackageManager); clearInvocations(getServices().alarmManager); @@ -6482,7 +6482,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { // To allow creation of Notification via Notification.Builder mContext.applicationInfo = mRealTestContext.getApplicationInfo(); - // Setup notification titles. + // Setup resources to render notification titles and texts. when(mServiceContext.resources .getString(R.string.personal_apps_suspension_title)) .thenReturn(PROFILE_OFF_SUSPENSION_TITLE); @@ -6490,14 +6490,19 @@ public class DevicePolicyManagerTest extends DpmTestBase { .getString(R.string.personal_apps_suspension_text)) .thenReturn(PROFILE_OFF_SUSPENSION_TEXT); when(mServiceContext.resources - .getString(R.string.personal_apps_suspension_tomorrow_text)) - .thenReturn(PROFILE_OFF_SUSPENSION_TOMORROW_TEXT); + .getString(eq(R.string.personal_apps_suspension_soon_text), + anyString(), anyString(), anyInt())) + .thenReturn(PROFILE_OFF_SUSPENSION_SOON_TEXT); + + // Make locale available for date formatting: + when(mServiceContext.resources.getConfiguration()) + .thenReturn(mRealTestContext.getResources().getConfiguration()); clearInvocations(getServices().ipackageManager); } private static Matcher<Notification> hasExtra(String... extras) { - assertEquals("Odd numebr of extra key-values", 0, extras.length % 2); + assertEquals("Odd number of extra key-values", 0, extras.length % 2); return new BaseMatcher<Notification>() { @Override public boolean matches(Object item) { diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java index 8f18f648f8f0..07050d9666d5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java @@ -52,6 +52,7 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import android.content.res.Configuration; +import android.graphics.Point; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.view.IWindowManager; @@ -432,20 +433,35 @@ public class AppWindowTokenTests extends WindowTestsBase { removeGlobalMinSizeRestriction(); final Rect stackBounds = new Rect(0, 0, 1000, 600); final Rect taskBounds = new Rect(100, 400, 600, 800); - mStack.setBounds(stackBounds); - mTask.setBounds(taskBounds); + // Set the bounds and windowing mode to window configuration directly, otherwise the + // testing setups may be discarded by configuration resolving. + mStack.getWindowConfiguration().setBounds(stackBounds); + mTask.getWindowConfiguration().setBounds(taskBounds); + mActivity.getWindowConfiguration().setBounds(taskBounds); // Check that anim bounds for freeform window match task bounds - mTask.setWindowingMode(WINDOWING_MODE_FREEFORM); + mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM); assertEquals(mTask.getBounds(), mActivity.getAnimationBounds(STACK_CLIP_NONE)); // STACK_CLIP_AFTER_ANIM should use task bounds since they will be clipped by // bounds animation layer. - mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FULLSCREEN); assertEquals(mTask.getBounds(), mActivity.getAnimationBounds(STACK_CLIP_AFTER_ANIM)); + // Even the activity is smaller than task and it is not aligned to the top-left corner of + // task, the animation bounds the same as task and position should be zero because in real + // case the letterbox will fill the remaining area in task. + final Rect halfBounds = new Rect(taskBounds); + halfBounds.scale(0.5f); + mActivity.getWindowConfiguration().setBounds(halfBounds); + final Point animationPosition = new Point(); + mActivity.getAnimationPosition(animationPosition); + + assertEquals(taskBounds, mActivity.getAnimationBounds(STACK_CLIP_AFTER_ANIM)); + assertEquals(new Point(0, 0), animationPosition); + // STACK_CLIP_BEFORE_ANIM should use stack bounds since it won't be clipped later. - mTask.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); assertEquals(mStack.getBounds(), mActivity.getAnimationBounds(STACK_CLIP_BEFORE_ANIM)); } |