diff options
46 files changed, 977 insertions, 382 deletions
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java index c3fc4d1603b0..e4306e5e2bda 100644 --- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java @@ -247,6 +247,28 @@ public interface AppStandbyInternal { int getBroadcastResponseFgThresholdState(); /** + * Returns the duration within which any broadcasts occurred will be treated as one broadcast + * session. + */ + long getBroadcastSessionsDurationMs(); + + /** + * Returns the duration within which any broadcasts occurred (with a corresponding response + * event) will be treated as one broadcast session. This similar to + * {@link #getBroadcastSessionsDurationMs()}, except that this duration will be used to group + * only broadcasts that have a corresponding response event into sessions. + */ + long getBroadcastSessionsWithResponseDurationMs(); + + /** + * Returns {@code true} if the response event should be attributed to all the broadcast + * sessions that occurred within the broadcast response window and {@code false} if the + * response event should be attributed to only the earliest broadcast session within the + * broadcast response window. + */ + boolean shouldNoteResponseEventForAllBroadcastSessions(); + + /** * Return the last known value corresponding to the {@code key} from * {@link android.provider.DeviceConfig#NAMESPACE_APP_STANDBY} in AppStandbyController. */ diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index 1891e06a9420..e57724909306 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -377,6 +377,32 @@ public class AppStandbyController ConstantsObserver.DEFAULT_BROADCAST_RESPONSE_FG_THRESHOLD_STATE; /** + * Duration (in millis) for the window within which any broadcasts occurred will be + * treated as one broadcast session. + */ + volatile long mBroadcastSessionsDurationMs = + ConstantsObserver.DEFAULT_BROADCAST_SESSIONS_DURATION_MS; + + /** + * Duration (in millis) for the window within which any broadcasts occurred ((with a + * corresponding response event) will be treated as one broadcast session. This similar to + * {@link #mBroadcastSessionsDurationMs}, except that this duration will be used to group only + * broadcasts that have a corresponding response event into sessions. + */ + volatile long mBroadcastSessionsWithResponseDurationMs = + ConstantsObserver.DEFAULT_BROADCAST_SESSIONS_WITH_RESPONSE_DURATION_MS; + + /** + * Denotes whether the response event should be attributed to all broadcast sessions or not. + * If this is {@code true}, then the response event should be attributed to all the broadcast + * sessions that occurred within the broadcast response window. Otherwise, the + * response event should be attributed to only the earliest broadcast session within the + * broadcast response window. + */ + volatile boolean mNoteResponseEventForAllBroadcastSessions = + ConstantsObserver.DEFAULT_NOTE_RESPONSE_EVENT_FOR_ALL_BROADCAST_SESSIONS; + + /** * Map of last known values of keys in {@link DeviceConfig#NAMESPACE_APP_STANDBY}. * * Note: We are intentionally not guarding this by any lock since this is only updated on @@ -1869,6 +1895,21 @@ public class AppStandbyController } @Override + public long getBroadcastSessionsDurationMs() { + return mBroadcastSessionsDurationMs; + } + + @Override + public long getBroadcastSessionsWithResponseDurationMs() { + return mBroadcastSessionsWithResponseDurationMs; + } + + @Override + public boolean shouldNoteResponseEventForAllBroadcastSessions() { + return mNoteResponseEventForAllBroadcastSessions; + } + + @Override @Nullable public String getAppStandbyConstant(@NonNull String key) { return mAppStandbyProperties.get(key); @@ -2202,6 +2243,18 @@ public class AppStandbyController pw.print(ActivityManager.procStateToString(mBroadcastResponseFgThresholdState)); pw.println(); + pw.print(" mBroadcastSessionsDurationMs="); + TimeUtils.formatDuration(mBroadcastSessionsDurationMs, pw); + pw.println(); + + pw.print(" mBroadcastSessionsWithResponseDurationMs="); + TimeUtils.formatDuration(mBroadcastSessionsWithResponseDurationMs, pw); + pw.println(); + + pw.print(" mNoteResponseEventForAllBroadcastSessions="); + pw.print(mNoteResponseEventForAllBroadcastSessions); + pw.println(); + pw.println(); pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled); pw.print(" mAllowRestrictedBucket="); @@ -2672,6 +2725,13 @@ public class AppStandbyController "broadcast_response_window_timeout_ms"; private static final String KEY_BROADCAST_RESPONSE_FG_THRESHOLD_STATE = "broadcast_response_fg_threshold_state"; + private static final String KEY_BROADCAST_SESSIONS_DURATION_MS = + "broadcast_sessions_duration_ms"; + private static final String KEY_BROADCAST_SESSIONS_WITH_RESPONSE_DURATION_MS = + "broadcast_sessions_with_response_duration_ms"; + private static final String KEY_NOTE_RESPONSE_EVENT_FOR_ALL_BROADCAST_SESSIONS = + "note_response_event_for_all_broadcast_sessions"; + public static final long DEFAULT_CHECK_IDLE_INTERVAL_MS = COMPRESS_TIME ? ONE_MINUTE : 4 * ONE_HOUR; public static final long DEFAULT_STRONG_USAGE_TIMEOUT = @@ -2705,6 +2765,12 @@ public class AppStandbyController 2 * ONE_MINUTE; public static final int DEFAULT_BROADCAST_RESPONSE_FG_THRESHOLD_STATE = ActivityManager.PROCESS_STATE_TOP; + public static final long DEFAULT_BROADCAST_SESSIONS_DURATION_MS = + 2 * ONE_MINUTE; + public static final long DEFAULT_BROADCAST_SESSIONS_WITH_RESPONSE_DURATION_MS = + 2 * ONE_MINUTE; + public static final boolean DEFAULT_NOTE_RESPONSE_EVENT_FOR_ALL_BROADCAST_SESSIONS = + true; ConstantsObserver(Handler handler) { super(handler); @@ -2832,6 +2898,21 @@ public class AppStandbyController KEY_BROADCAST_RESPONSE_FG_THRESHOLD_STATE, DEFAULT_BROADCAST_RESPONSE_FG_THRESHOLD_STATE); break; + case KEY_BROADCAST_SESSIONS_DURATION_MS: + mBroadcastSessionsDurationMs = properties.getLong( + KEY_BROADCAST_SESSIONS_DURATION_MS, + DEFAULT_BROADCAST_SESSIONS_DURATION_MS); + break; + case KEY_BROADCAST_SESSIONS_WITH_RESPONSE_DURATION_MS: + mBroadcastSessionsWithResponseDurationMs = properties.getLong( + KEY_BROADCAST_SESSIONS_WITH_RESPONSE_DURATION_MS, + DEFAULT_BROADCAST_SESSIONS_WITH_RESPONSE_DURATION_MS); + break; + case KEY_NOTE_RESPONSE_EVENT_FOR_ALL_BROADCAST_SESSIONS: + mNoteResponseEventForAllBroadcastSessions = properties.getBoolean( + KEY_NOTE_RESPONSE_EVENT_FOR_ALL_BROADCAST_SESSIONS, + DEFAULT_NOTE_RESPONSE_EVENT_FOR_ALL_BROADCAST_SESSIONS); + break; default: if (!timeThresholdsUpdated && (name.startsWith(KEY_PREFIX_SCREEN_TIME_THRESHOLD) diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 8647b9a2967c..93f91e5af3eb 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -11413,7 +11413,9 @@ public class DevicePolicyManager { /** * Called by a device owner or a profile owner of an organization-owned managed profile to - * control whether the user can change networks configured by the admin. + * control whether the user can change networks configured by the admin. When this lockdown is + * enabled, the user can still configure and connect to other Wi-Fi networks, or use other Wi-Fi + * capabilities such as tethering. * <p> * WiFi network configuration lockdown is controlled by a global settings * {@link android.provider.Settings.Global#WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN} and calling diff --git a/core/java/android/app/usage/OWNERS b/core/java/android/app/usage/OWNERS index 9668f80e562e..a4bf98504591 100644 --- a/core/java/android/app/usage/OWNERS +++ b/core/java/android/app/usage/OWNERS @@ -5,3 +5,4 @@ mwachens@google.com varunshah@google.com per-file *StorageStats* = file:/core/java/android/os/storage/OWNERS +per-file *Broadcast* = sudheersai@google.com diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 8f2d218a20c1..13ca2c34b27e 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -463,21 +463,22 @@ public final class PowerManager { /** * @hide */ - public static String sleepReasonToString(int sleepReason) { + public static String sleepReasonToString(@GoToSleepReason int sleepReason) { switch (sleepReason) { + case GO_TO_SLEEP_REASON_ACCESSIBILITY: return "accessibility"; case GO_TO_SLEEP_REASON_APPLICATION: return "application"; case GO_TO_SLEEP_REASON_DEVICE_ADMIN: return "device_admin"; - case GO_TO_SLEEP_REASON_TIMEOUT: return "timeout"; + case GO_TO_SLEEP_REASON_DEVICE_FOLD: return "device_folded"; + case GO_TO_SLEEP_REASON_DISPLAY_GROUP_REMOVED: return "display_group_removed"; + case GO_TO_SLEEP_REASON_DISPLAY_GROUPS_TURNED_OFF: return "display_groups_turned_off"; + case GO_TO_SLEEP_REASON_FORCE_SUSPEND: return "force_suspend"; + case GO_TO_SLEEP_REASON_HDMI: return "hdmi"; + case GO_TO_SLEEP_REASON_INATTENTIVE: return "inattentive"; case GO_TO_SLEEP_REASON_LID_SWITCH: return "lid_switch"; case GO_TO_SLEEP_REASON_POWER_BUTTON: return "power_button"; - case GO_TO_SLEEP_REASON_HDMI: return "hdmi"; + case GO_TO_SLEEP_REASON_QUIESCENT: return "quiescent"; case GO_TO_SLEEP_REASON_SLEEP_BUTTON: return "sleep_button"; - case GO_TO_SLEEP_REASON_ACCESSIBILITY: return "accessibility"; - case GO_TO_SLEEP_REASON_FORCE_SUSPEND: return "force_suspend"; - case GO_TO_SLEEP_REASON_INATTENTIVE: return "inattentive"; - case GO_TO_SLEEP_REASON_DISPLAY_GROUP_REMOVED: return "display_group_removed"; - case GO_TO_SLEEP_REASON_DISPLAY_GROUPS_TURNED_OFF: return "display_groups_turned_off"; - case GO_TO_SLEEP_REASON_DEVICE_FOLD: return "device_folded"; + case GO_TO_SLEEP_REASON_TIMEOUT: return "timeout"; default: return Integer.toString(sleepReason); } } @@ -576,18 +577,20 @@ public final class PowerManager { * @hide */ @IntDef(prefix = { "GO_TO_SLEEP_REASON_" }, value = { + GO_TO_SLEEP_REASON_ACCESSIBILITY, GO_TO_SLEEP_REASON_APPLICATION, GO_TO_SLEEP_REASON_DEVICE_ADMIN, - GO_TO_SLEEP_REASON_TIMEOUT, - GO_TO_SLEEP_REASON_LID_SWITCH, - GO_TO_SLEEP_REASON_POWER_BUTTON, - GO_TO_SLEEP_REASON_HDMI, - GO_TO_SLEEP_REASON_SLEEP_BUTTON, - GO_TO_SLEEP_REASON_ACCESSIBILITY, + GO_TO_SLEEP_REASON_DEVICE_FOLD, + GO_TO_SLEEP_REASON_DISPLAY_GROUP_REMOVED, + GO_TO_SLEEP_REASON_DISPLAY_GROUPS_TURNED_OFF, GO_TO_SLEEP_REASON_FORCE_SUSPEND, + GO_TO_SLEEP_REASON_HDMI, GO_TO_SLEEP_REASON_INATTENTIVE, + GO_TO_SLEEP_REASON_LID_SWITCH, + GO_TO_SLEEP_REASON_POWER_BUTTON, GO_TO_SLEEP_REASON_QUIESCENT, - GO_TO_SLEEP_REASON_DEVICE_FOLD + GO_TO_SLEEP_REASON_SLEEP_BUTTON, + GO_TO_SLEEP_REASON_TIMEOUT, }) @Retention(RetentionPolicy.SOURCE) public @interface GoToSleepReason{} @@ -704,6 +707,8 @@ public final class PowerManager { } /** + * Information related to the device waking up, triggered by {@link #wakeUp}. + * * @hide */ public static class WakeData { @@ -712,9 +717,9 @@ public final class PowerManager { this.wakeReason = wakeReason; this.sleepDuration = sleepDuration; } - public long wakeTime; - public @WakeReason int wakeReason; - public long sleepDuration; + public final long wakeTime; + public final @WakeReason int wakeReason; + public final long sleepDuration; @Override public boolean equals(@Nullable Object o) { @@ -733,6 +738,35 @@ public final class PowerManager { } /** + * Information related to the device going to sleep, triggered by {@link #goToSleep}. + * + * @hide + */ + public static class SleepData { + public SleepData(long goToSleepUptimeMillis, @GoToSleepReason int goToSleepReason) { + this.goToSleepUptimeMillis = goToSleepUptimeMillis; + this.goToSleepReason = goToSleepReason; + } + public final long goToSleepUptimeMillis; + public final @GoToSleepReason int goToSleepReason; + + @Override + public boolean equals(@Nullable Object o) { + if (o instanceof SleepData) { + final SleepData other = (SleepData) o; + return goToSleepUptimeMillis == other.goToSleepUptimeMillis + && goToSleepReason == other.goToSleepReason; + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(goToSleepUptimeMillis, goToSleepReason); + } + } + + /** * The value to pass as the 'reason' argument to reboot() to reboot into * recovery mode for tasks other than applying system updates, such as * doing factory resets. @@ -2644,6 +2678,7 @@ public final class PowerManager { * * @hide */ + @GoToSleepReason public int getLastSleepReason() { try { return mService.getLastSleepReason(); diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java index ec4d3b6a2441..5ca0da2d3f97 100644 --- a/core/java/android/os/PowerManagerInternal.java +++ b/core/java/android/os/PowerManagerInternal.java @@ -330,6 +330,9 @@ public abstract class PowerManagerInternal { /** Returns information about the last wakeup event. */ public abstract PowerManager.WakeData getLastWakeup(); + /** Returns information about the last event to go to sleep. */ + public abstract PowerManager.SleepData getLastGoToSleep(); + /** Allows power button to intercept a power key button press. */ public abstract boolean interceptPowerKeyDown(KeyEvent event); } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 196f2f94120e..0ffdfc6cbcb1 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -257,7 +257,8 @@ public class UserManager { public static final String DISALLOW_MODIFY_ACCOUNTS = "no_modify_accounts"; /** - * Specifies if a user is disallowed from changing Wi-Fi access points via Settings. + * Specifies if a user is disallowed from changing Wi-Fi access points via Settings. This + * restriction does not affect Wi-Fi tethering settings. * * <p>A device owner and a profile owner can set this restriction, although the restriction has * no effect in a managed profile. When it is set by a device owner, a profile owner on the @@ -295,6 +296,9 @@ public class UserManager { /** * Specifies if a user is disallowed from using Wi-Fi tethering. * + * <p>This restriction does not limit the user's ability to modify or connect to regular + * Wi-Fi networks, which is separately controlled by {@link #DISALLOW_CONFIG_WIFI}. + * * <p>This restriction can only be set by a device owner, * a profile owner of an organization-owned managed profile on the parent profile. * When it is set by any of these owners, it prevents all users from using diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index 06d12b5195ab..3436b9e75c65 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -682,7 +682,6 @@ public class SystemConfig { readPermissions(parser, Environment.buildPath(f, "etc", "permissions"), apexPermissionFlag); } - pruneVendorApexPrivappAllowlists(); } @VisibleForTesting @@ -1598,21 +1597,6 @@ public class SystemConfig { } } - /** - * Prunes out any privileged permission allowlists bundled in vendor apexes. - */ - @VisibleForTesting - public void pruneVendorApexPrivappAllowlists() { - for (String moduleName: mAllowedVendorApexes.keySet()) { - if (mApexPrivAppPermissions.containsKey(moduleName) - || mApexPrivAppDenyPermissions.containsKey(moduleName)) { - Slog.w(TAG, moduleName + " is a vendor apex, ignore its priv-app allowlist"); - mApexPrivAppPermissions.remove(moduleName); - mApexPrivAppDenyPermissions.remove(moduleName); - } - } - } - private void readInstallInUserType(XmlPullParser parser, Map<String, Set<String>> doInstallMap, Map<String, Set<String>> nonInstallMap) diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto index 2f2158d4d5a0..2a625b027c17 100644 --- a/core/proto/android/server/vibrator/vibratormanagerservice.proto +++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto @@ -86,7 +86,7 @@ message VibrationAttributesProto { optional int32 flags = 3; } -// Next id: 7 +// Next id: 8 message VibrationProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; optional int64 start_time = 1; @@ -95,6 +95,7 @@ message VibrationProto { optional CombinedVibrationEffectProto original_effect = 4; optional VibrationAttributesProto attributes = 5; optional int32 status = 6; + optional int64 duration_ms = 7; } // Next id: 25 diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 04ead1bf1e9c..58a2073981bf 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -33,30 +33,6 @@ applications that come with the platform <permission name="android.permission.WRITE_SECURE_SETTINGS"/> </privapp-permissions> - <privapp-permissions package="com.android.bluetooth.services"> - <permission name="android.permission.DUMP"/> - <permission name="android.permission.MODIFY_AUDIO_ROUTING"/> - <permission name="android.permission.WRITE_SECURE_SETTINGS"/> - <permission name="android.permission.TETHER_PRIVILEGED"/> - <permission name="android.permission.CALL_PRIVILEGED"/> - <permission name="android.permission.MODIFY_PHONE_STATE"/> - <permission name="android.permission.INTERACT_ACROSS_USERS"/> - <permission name="android.permission.INTERACT_ACROSS_USERS_FULL"/> - <permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/> - <permission name="android.permission.UPDATE_DEVICE_STATS"/> - <permission name="android.permission.PACKAGE_USAGE_STATS"/> - <permission name="android.permission.NFC_HANDOVER_STATUS"/> - <permission name="android.permission.CONNECTIVITY_INTERNAL"/> - <permission name="android.permission.BLUETOOTH_PRIVILEGED"/> - <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> - <permission name="android.permission.MEDIA_CONTENT_CONTROL"/> - <permission name="android.permission.REAL_GET_TASKS"/> - <permission name="android.permission.MANAGE_USERS"/> - <permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"/> - <permission name="android.permission.WRITE_APN_SETTINGS"/> - <permission name="android.permission.UPDATE_APP_OPS_STATS"/> - </privapp-permissions> - <privapp-permissions package="com.android.backupconfirm"> <permission name="android.permission.BACKUP"/> <permission name="android.permission.CRYPT_KEEPER"/> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index a45931953c0b..f427a2c4bc95 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -66,6 +66,7 @@ import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import android.os.UserManager; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.RankingMap; import android.util.ArraySet; @@ -147,6 +148,7 @@ public class BubbleController { private final FloatingContentCoordinator mFloatingContentCoordinator; private final BubbleDataRepository mDataRepository; private final WindowManagerShellWrapper mWindowManagerShellWrapper; + private final UserManager mUserManager; private final LauncherApps mLauncherApps; private final IStatusBarService mBarService; private final WindowManager mWindowManager; @@ -231,6 +233,7 @@ public class BubbleController { @Nullable IStatusBarService statusBarService, WindowManager windowManager, WindowManagerShellWrapper windowManagerShellWrapper, + UserManager userManager, LauncherApps launcherApps, TaskStackListenerImpl taskStackListener, UiEventLogger uiEventLogger, @@ -248,8 +251,8 @@ public class BubbleController { BubbleData data = new BubbleData(context, logger, positioner, mainExecutor); return new BubbleController(context, data, synchronizer, floatingContentCoordinator, new BubbleDataRepository(context, launcherApps, mainExecutor), - statusBarService, windowManager, windowManagerShellWrapper, launcherApps, - logger, taskStackListener, organizer, positioner, displayController, + statusBarService, windowManager, windowManagerShellWrapper, userManager, + launcherApps, logger, taskStackListener, organizer, positioner, displayController, oneHandedOptional, dragAndDropController, mainExecutor, mainHandler, bgExecutor, taskViewTransitions, syncQueue); } @@ -266,6 +269,7 @@ public class BubbleController { @Nullable IStatusBarService statusBarService, WindowManager windowManager, WindowManagerShellWrapper windowManagerShellWrapper, + UserManager userManager, LauncherApps launcherApps, BubbleLogger bubbleLogger, TaskStackListenerImpl taskStackListener, @@ -287,6 +291,7 @@ public class BubbleController { : statusBarService; mWindowManager = windowManager; mWindowManagerShellWrapper = windowManagerShellWrapper; + mUserManager = userManager; mFloatingContentCoordinator = floatingContentCoordinator; mDataRepository = dataRepository; mLogger = bubbleLogger; @@ -447,6 +452,10 @@ public class BubbleController { mOneHandedOptional.ifPresent(this::registerOneHandedState); mDragAndDropController.addListener(this::collapseStack); + + // Clear out any persisted bubbles on disk that no longer have a valid user. + List<UserInfo> users = mUserManager.getAliveUsers(); + mDataRepository.sanitizeBubbles(users); } @VisibleForTesting @@ -590,6 +599,17 @@ public class BubbleController { mCurrentProfiles = currentProfiles; } + /** Called when a user is removed from the device, including work profiles. */ + public void onUserRemoved(int removedUserId) { + UserInfo parent = mUserManager.getProfileParent(removedUserId); + int parentUserId = parent != null ? parent.getUserHandle().getIdentifier() : -1; + mBubbleData.removeBubblesForUser(removedUserId); + // Typically calls from BubbleData would remove bubbles from the DataRepository as well, + // however, this gets complicated when users are removed (mCurrentUserId won't necessarily + // be correct for this) so we update the repo directly. + mDataRepository.removeBubblesForUser(removedUserId, parentUserId); + } + /** Whether this userId belongs to the current user. */ private boolean isCurrentProfile(int userId) { return userId == UserHandle.USER_ALL @@ -1809,6 +1829,13 @@ public class BubbleController { } @Override + public void onUserRemoved(int removedUserId) { + mMainExecutor.execute(() -> { + BubbleController.this.onUserRemoved(removedUserId); + }); + } + + @Override public void onConfigChanged(Configuration newConfig) { mMainExecutor.execute(() -> { BubbleController.this.onConfigChanged(newConfig); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index e4a0fd03860c..fa86c8436647 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -465,7 +465,7 @@ public class BubbleData { getOverflowBubbles(), invalidBubblesFromPackage, removeBubble); } - /** Dismisses all bubbles from the given package. */ + /** Removes all bubbles from the given package. */ public void removeBubblesWithPackageName(String packageName, int reason) { final Predicate<Bubble> bubbleMatchesPackage = bubble -> bubble.getPackageName().equals(packageName); @@ -477,6 +477,18 @@ public class BubbleData { performActionOnBubblesMatching(getOverflowBubbles(), bubbleMatchesPackage, removeBubble); } + /** Removes all bubbles for the given user. */ + public void removeBubblesForUser(int userId) { + List<Bubble> removedBubbles = filterAllBubbles(bubble -> + userId == bubble.getUser().getIdentifier()); + for (Bubble b : removedBubbles) { + doRemove(b.getKey(), Bubbles.DISMISS_USER_REMOVED); + } + if (!removedBubbles.isEmpty()) { + dispatchPendingChanges(); + } + } + private void doAdd(Bubble bubble) { if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "doAdd: " + bubble); @@ -552,7 +564,8 @@ public class BubbleData { || reason == Bubbles.DISMISS_BLOCKED || reason == Bubbles.DISMISS_SHORTCUT_REMOVED || reason == Bubbles.DISMISS_PACKAGE_REMOVED - || reason == Bubbles.DISMISS_USER_CHANGED; + || reason == Bubbles.DISMISS_USER_CHANGED + || reason == Bubbles.DISMISS_USER_REMOVED; int indexToRemove = indexForKey(key); if (indexToRemove == -1) { @@ -1073,6 +1086,35 @@ public class BubbleData { return null; } + /** + * Returns a list of bubbles that match the provided predicate. This checks all types of + * bubbles (i.e. pending, suppressed, active, and overflowed). + */ + private List<Bubble> filterAllBubbles(Predicate<Bubble> predicate) { + ArrayList<Bubble> matchingBubbles = new ArrayList<>(); + for (Bubble b : mPendingBubbles.values()) { + if (predicate.test(b)) { + matchingBubbles.add(b); + } + } + for (Bubble b : mSuppressedBubbles.values()) { + if (predicate.test(b)) { + matchingBubbles.add(b); + } + } + for (Bubble b : mBubbles) { + if (predicate.test(b)) { + matchingBubbles.add(b); + } + } + for (Bubble b : mOverflowBubbles) { + if (predicate.test(b)) { + matchingBubbles.add(b); + } + } + return matchingBubbles; + } + @VisibleForTesting(visibility = PRIVATE) void setTimeSource(TimeSource timeSource) { mTimeSource = timeSource; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt index 9d9e442affd3..97560f44fb06 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt @@ -22,6 +22,7 @@ import android.content.pm.LauncherApps import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER +import android.content.pm.UserInfo import android.os.UserHandle import android.util.Log import com.android.wm.shell.bubbles.storage.BubbleEntity @@ -73,6 +74,22 @@ internal class BubbleDataRepository( if (entities.isNotEmpty()) persistToDisk() } + /** + * Removes all the bubbles associated with the provided user from memory. Then persists the + * snapshot to disk asynchronously. + */ + fun removeBubblesForUser(@UserIdInt userId: Int, @UserIdInt parentId: Int) { + if (volatileRepository.removeBubblesForUser(userId, parentId)) persistToDisk() + } + + /** + * Remove any bubbles that don't have a user id from the provided list of users. + */ + fun sanitizeBubbles(users: List<UserInfo>) { + val userIds = users.map { u -> u.id } + if (volatileRepository.sanitizeBubbles(userIds)) persistToDisk() + } + private fun transform(bubbles: List<Bubble>): List<BubbleEntity> { return bubbles.mapNotNull { b -> BubbleEntity( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index c7db8d8d1646..8a0db0a12711 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -57,7 +57,7 @@ public interface Bubbles { DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE, DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT, DISMISS_OVERFLOW_MAX_REACHED, DISMISS_SHORTCUT_REMOVED, DISMISS_PACKAGE_REMOVED, - DISMISS_NO_BUBBLE_UP, DISMISS_RELOAD_FROM_DISK}) + DISMISS_NO_BUBBLE_UP, DISMISS_RELOAD_FROM_DISK, DISMISS_USER_REMOVED}) @Target({FIELD, LOCAL_VARIABLE, PARAMETER}) @interface DismissReason {} @@ -76,6 +76,7 @@ public interface Bubbles { int DISMISS_PACKAGE_REMOVED = 13; int DISMISS_NO_BUBBLE_UP = 14; int DISMISS_RELOAD_FROM_DISK = 15; + int DISMISS_USER_REMOVED = 16; /** * @return {@code true} if there is a bubble associated with the provided key and if its @@ -243,6 +244,13 @@ public interface Bubbles { void onCurrentProfilesChanged(SparseArray<UserInfo> currentProfiles); /** + * Called when a user is removed. + * + * @param removedUserId the id of the removed user. + */ + void onUserRemoved(int removedUserId); + + /** * Called when config changed. * * @param newConfig the new config. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepository.kt index a5267d8be9fe..0d3ba9a78e35 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepository.kt @@ -15,6 +15,7 @@ */ package com.android.wm.shell.bubbles.storage +import android.annotation.UserIdInt import android.content.pm.LauncherApps import android.os.UserHandle import android.util.SparseArray @@ -95,10 +96,63 @@ class BubbleVolatileRepository(private val launcherApps: LauncherApps) { } @Synchronized - fun removeBubbles(userId: Int, bubbles: List<BubbleEntity>) = + fun removeBubbles(@UserIdInt userId: Int, bubbles: List<BubbleEntity>) = uncache(bubbles.filter { b: BubbleEntity -> getEntities(userId).removeIf { e: BubbleEntity -> b.key == e.key } }) + /** + * Removes all the bubbles associated with the provided userId. + * @return whether bubbles were removed or not. + */ + @Synchronized + fun removeBubblesForUser(@UserIdInt userId: Int, @UserIdInt parentUserId: Int): Boolean { + if (parentUserId != -1) { + return removeBubblesForUserWithParent(userId, parentUserId) + } else { + val entities = entitiesByUser.get(userId) + entitiesByUser.remove(userId) + return entities != null + } + } + + /** + * Removes all the bubbles associated with the provided userId when that userId is part of + * a profile (e.g. managed account). + * + * @return whether bubbles were removed or not. + */ + @Synchronized + private fun removeBubblesForUserWithParent( + @UserIdInt userId: Int, + @UserIdInt parentUserId: Int + ): Boolean { + return entitiesByUser.get(parentUserId).removeIf { b: BubbleEntity -> b.userId == userId } + } + + /** + * Goes through all the persisted bubbles and removes them if the user is not in the active + * list of users. + * + * @return whether the list of bubbles changed or not (i.e. was a removal made). + */ + @Synchronized + fun sanitizeBubbles(activeUsers: List<Int>): Boolean { + for (i in 0 until entitiesByUser.size()) { + // First check if the user is a parent / top-level user + val parentUserId = entitiesByUser.keyAt(i) + if (!activeUsers.contains(parentUserId)) { + return removeBubblesForUser(parentUserId, -1) + } else { + // Then check if each of the bubbles in the top-level user, still has a valid user + // as it could belong to a profile and have a different id from the parent. + return entitiesByUser.get(parentUserId).removeIf { b: BubbleEntity -> + !activeUsers.contains(b.userId) + } + } + } + return false + } + private fun cache(bubbles: List<BubbleEntity>) { bubbles.groupBy { ShortcutKey(it.userId, it.packageName) }.forEach { (key, bubbles) -> launcherApps.cacheShortcuts(key.pkg, bubbles.map { it.shortcutId }, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 98de60c419b3..b3799e2cf8d9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -20,6 +20,7 @@ import android.animation.AnimationHandler; import android.content.Context; import android.content.pm.LauncherApps; import android.os.Handler; +import android.os.UserManager; import android.view.WindowManager; import com.android.internal.jank.InteractionJankMonitor; @@ -106,6 +107,7 @@ public class WMShellModule { IStatusBarService statusBarService, WindowManager windowManager, WindowManagerShellWrapper windowManagerShellWrapper, + UserManager userManager, LauncherApps launcherApps, TaskStackListenerImpl taskStackListener, UiEventLogger uiEventLogger, @@ -120,7 +122,7 @@ public class WMShellModule { SyncTransactionQueue syncQueue) { return BubbleController.create(context, null /* synchronizer */, floatingContentCoordinator, statusBarService, windowManager, - windowManagerShellWrapper, launcherApps, taskStackListener, + windowManagerShellWrapper, userManager, launcherApps, taskStackListener, uiEventLogger, organizer, displayController, oneHandedOptional, dragAndDropController, mainExecutor, mainHandler, bgExecutor, taskViewTransitions, syncQueue); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index c3369e4bb90b..49b6968f9417 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -254,6 +254,7 @@ public class BackAnimationControllerTest { verify(mIOnBackInvokedCallback, never()).onBackInvoked(); } + @Test public void ignoresGesture_transitionInProgress() throws RemoteException { mController.setBackToLauncherCallback(mIOnBackInvokedCallback); RemoteAnimationTarget animationTarget = createAnimationTarget(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java index bde94d9d6c29..e6711aca19c1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java @@ -129,8 +129,8 @@ public class BubbleDataTest extends ShellTestCase { mEntryA3 = createBubbleEntry(1, "a3", "package.a", null); mEntryB1 = createBubbleEntry(1, "b1", "package.b", null); mEntryB2 = createBubbleEntry(1, "b2", "package.b", null); - mEntryB3 = createBubbleEntry(1, "b3", "package.b", null); - mEntryC1 = createBubbleEntry(1, "c1", "package.c", null); + mEntryB3 = createBubbleEntry(11, "b3", "package.b", null); + mEntryC1 = createBubbleEntry(11, "c1", "package.c", null); NotificationListenerService.Ranking ranking = mock(NotificationListenerService.Ranking.class); @@ -1058,6 +1058,37 @@ public class BubbleDataTest extends ShellTestCase { assertBubbleListContains(mBubbleA2, mBubbleA1, mBubbleLocusId); } + @Test + public void test_removeBubblesForUser() { + // A is user 1 + sendUpdatedEntryAtTime(mEntryA1, 2000); + sendUpdatedEntryAtTime(mEntryA2, 3000); + // B & C belong to user 11 + sendUpdatedEntryAtTime(mEntryB3, 4000); + sendUpdatedEntryAtTime(mEntryC1, 5000); + mBubbleData.setListener(mListener); + + mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_USER_GESTURE); + verifyUpdateReceived(); + assertOverflowChangedTo(ImmutableList.of(mBubbleA1)); + assertBubbleListContains(mBubbleC1, mBubbleB3, mBubbleA2); + + // Remove all the A bubbles + mBubbleData.removeBubblesForUser(1); + verifyUpdateReceived(); + + // Verify the update has the removals. + BubbleData.Update update = mUpdateCaptor.getValue(); + assertThat(update.removedBubbles.get(0)).isEqualTo( + Pair.create(mBubbleA2, Bubbles.DISMISS_USER_REMOVED)); + assertThat(update.removedBubbles.get(1)).isEqualTo( + Pair.create(mBubbleA1, Bubbles.DISMISS_USER_REMOVED)); + + // Verify no A bubbles in active or overflow. + assertBubbleListContains(mBubbleC1, mBubbleB3); + assertOverflowChangedTo(ImmutableList.of()); + } + private void verifyUpdateReceived() { verify(mListener).applyUpdate(mUpdateCaptor.capture()); reset(mListener); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt index bfdf5208bbf0..77c70557fb70 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt @@ -23,14 +23,19 @@ import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import org.junit.Test import com.android.wm.shell.ShellTestCase +import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertEquals import org.junit.Before import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyString import org.mockito.ArgumentMatchers.eq import org.mockito.Mockito import org.mockito.Mockito.mock -import org.mockito.Mockito.verify +import org.mockito.Mockito.never import org.mockito.Mockito.reset +import org.mockito.Mockito.verify @SmallTest @RunWith(AndroidTestingRunner::class) @@ -41,17 +46,17 @@ class BubbleVolatileRepositoryTest : ShellTestCase() { private val user11 = UserHandle.of(11) // user, package, shortcut, notification key, height, res-height, title, taskId, locusId - private val bubble1 = BubbleEntity(0, "com.example.messenger", "shortcut-1", - "0key-1", 120, 0, null, 1, null) - private val bubble2 = BubbleEntity(10, "com.example.chat", "alice and bob", - "10key-2", 0, 16537428, "title", 2, null) - private val bubble3 = BubbleEntity(0, "com.example.messenger", "shortcut-2", - "0key-3", 120, 0, null, INVALID_TASK_ID, null) - - private val bubble11 = BubbleEntity(11, "com.example.messenger", - "shortcut-1", "01key-1", 120, 0, null, 3) - private val bubble12 = BubbleEntity(11, "com.example.chat", "alice and bob", - "11key-2", 0, 16537428, "title", INVALID_TASK_ID) + private val bubble1 = BubbleEntity(user0.identifier, + "com.example.messenger", "shortcut-1", "0key-1", 120, 0, null, 1, null) + private val bubble2 = BubbleEntity(user10_managed.identifier, + "com.example.chat", "alice and bob", "10key-2", 0, 16537428, "title", 2, null) + private val bubble3 = BubbleEntity(user0.identifier, + "com.example.messenger", "shortcut-2", "0key-3", 120, 0, null, INVALID_TASK_ID, null) + + private val bubble11 = BubbleEntity(user11.identifier, + "com.example.messenger", "shortcut-1", "01key-1", 120, 0, null, 3) + private val bubble12 = BubbleEntity(user11.identifier, + "com.example.chat", "alice and bob", "11key-2", 0, 16537428, "title", INVALID_TASK_ID) private val user0bubbles = listOf(bubble1, bubble2, bubble3) private val user11bubbles = listOf(bubble11, bubble12) @@ -151,6 +156,119 @@ class BubbleVolatileRepositoryTest : ShellTestCase() { repository.addBubbles(user0.identifier, listOf(bubbleModified)) assertEquals(bubbleModified, repository.getEntities(user0.identifier).get(0)) } + + @Test + fun testRemoveBubblesForUser() { + repository.addBubbles(user0.identifier, user0bubbles) + assertThat(repository.getEntities(user0.identifier).toList()) + .isEqualTo(listOf(bubble1, bubble2, bubble3)) + + val ret = repository.removeBubblesForUser(user0.identifier, -1) + assertThat(ret).isTrue() // bubbles were removed + + assertThat(repository.getEntities(user0.identifier).toList()).isEmpty() + verify(launcherApps, never()).uncacheShortcuts(anyString(), + any(), + any(UserHandle::class.java), anyInt()) + } + + @Test + fun testRemoveBubblesForUser_parentUserRemoved() { + repository.addBubbles(user0.identifier, user0bubbles) + // bubble2 is the work profile bubble + assertThat(repository.getEntities(user0.identifier).toList()) + .isEqualTo(listOf(bubble1, bubble2, bubble3)) + + val ret = repository.removeBubblesForUser(user10_managed.identifier, user0.identifier) + assertThat(ret).isTrue() // bubbles were removed + + assertThat(repository.getEntities(user0.identifier).toList()) + .isEqualTo(listOf(bubble1, bubble3)) + verify(launcherApps, never()).uncacheShortcuts(anyString(), + any(), + any(UserHandle::class.java), anyInt()) + } + + @Test + fun testRemoveBubblesForUser_withoutBubbles() { + repository.addBubbles(user0.identifier, user0bubbles) + assertThat(repository.getEntities(user0.identifier).toList()) + .isEqualTo(listOf(bubble1, bubble2, bubble3)) + + val ret = repository.removeBubblesForUser(user11.identifier, -1) + assertThat(ret).isFalse() // bubbles were NOT removed + + assertThat(repository.getEntities(user0.identifier).toList()) + .isEqualTo(listOf(bubble1, bubble2, bubble3)) + verify(launcherApps, never()).uncacheShortcuts(anyString(), + any(), + any(UserHandle::class.java), anyInt()) + } + + @Test + fun testSanitizeBubbles_noChanges() { + repository.addBubbles(user0.identifier, user0bubbles) + assertThat(repository.getEntities(user0.identifier).toList()) + .isEqualTo(listOf(bubble1, bubble2, bubble3)) + repository.addBubbles(user11.identifier, user11bubbles) + assertThat(repository.getEntities(user11.identifier).toList()) + .isEqualTo(listOf(bubble11, bubble12)) + + val ret = repository.sanitizeBubbles(listOf(user0.identifier, + user10_managed.identifier, + user11.identifier)) + assertThat(ret).isFalse() // bubbles were NOT removed + + verify(launcherApps, never()).uncacheShortcuts(anyString(), + any(), + any(UserHandle::class.java), anyInt()) + } + + @Test + fun testSanitizeBubbles_userRemoved() { + repository.addBubbles(user0.identifier, user0bubbles) + assertThat(repository.getEntities(user0.identifier).toList()) + .isEqualTo(listOf(bubble1, bubble2, bubble3)) + repository.addBubbles(user11.identifier, user11bubbles) + assertThat(repository.getEntities(user11.identifier).toList()) + .isEqualTo(listOf(bubble11, bubble12)) + + val ret = repository.sanitizeBubbles(listOf(user11.identifier)) + assertThat(ret).isTrue() // bubbles were removed + + assertThat(repository.getEntities(user0.identifier).toList()).isEmpty() + verify(launcherApps, never()).uncacheShortcuts(anyString(), + any(), + any(UserHandle::class.java), anyInt()) + + // User 11 bubbles should still be here + assertThat(repository.getEntities(user11.identifier).toList()) + .isEqualTo(listOf(bubble11, bubble12)) + } + + @Test + fun testSanitizeBubbles_userParentRemoved() { + repository.addBubbles(user0.identifier, user0bubbles) + assertThat(repository.getEntities(user0.identifier).toList()) + .isEqualTo(listOf(bubble1, bubble2, bubble3)) + + repository.addBubbles(user11.identifier, user11bubbles) + assertThat(repository.getEntities(user11.identifier).toList()) + .isEqualTo(listOf(bubble11, bubble12)) + + val ret = repository.sanitizeBubbles(listOf(user0.identifier, user11.identifier)) + assertThat(ret).isTrue() // bubbles were removed + // bubble2 is the work profile bubble and should be removed + assertThat(repository.getEntities(user0.identifier).toList()) + .isEqualTo(listOf(bubble1, bubble3)) + verify(launcherApps, never()).uncacheShortcuts(anyString(), + any(), + any(UserHandle::class.java), anyInt()) + + // User 11 bubbles should still be here + assertThat(repository.getEntities(user11.identifier).toList()) + .isEqualTo(listOf(bubble11, bubble12)) + } } private const val PKG_MESSENGER = "com.example.messenger" diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java index 3d2f570bde87..894bb5fc8577 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java @@ -181,7 +181,7 @@ public interface VolumeDialogController { public interface Callbacks { int VERSION = 1; - void onShowRequested(int reason); + void onShowRequested(int reason, boolean keyguardLocked, int lockTaskModeState); void onDismissRequested(int reason); void onStateChanged(State state); void onLayoutDirectionChanged(int layoutDirection); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java index 5fd9671bda01..512230ee54a6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java @@ -98,6 +98,7 @@ public interface NotificationLockscreenUserManager { interface UserChangedListener { default void onUserChanged(int userId) {} default void onCurrentProfilesChanged(SparseArray<UserInfo> currentProfiles) {} + default void onUserRemoved(int userId) {} } /** Used to hide notifications on the lockscreen */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 334cfe5f4c41..0394724dd597 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -152,6 +152,15 @@ public class NotificationLockscreenUserManagerImpl implements listener.onUserChanged(mCurrentUserId); } break; + case Intent.ACTION_USER_REMOVED: + int removedUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (removedUserId != -1) { + for (UserChangedListener listener : mListeners) { + listener.onUserRemoved(removedUserId); + } + } + updateCurrentProfilesCache(); + break; case Intent.ACTION_USER_ADDED: case Intent.ACTION_MANAGED_PROFILE_AVAILABLE: case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE: @@ -303,6 +312,7 @@ public class NotificationLockscreenUserManagerImpl implements IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_SWITCHED); filter.addAction(Intent.ACTION_USER_ADDED); + filter.addAction(Intent.ACTION_USER_REMOVED); filter.addAction(Intent.ACTION_USER_UNLOCKED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java index 190e655ae403..f71d98827e4b 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java @@ -152,7 +152,7 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna private void applyConfiguration() { mController.setVolumePolicy(mVolumePolicy); - mController.showDndTile(true); + mController.showDndTile(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java index 59aa4d6fcb8b..bf7c4598b5da 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java @@ -18,6 +18,8 @@ package com.android.systemui.volume; import static android.media.AudioManager.RINGER_MODE_NORMAL; +import android.app.ActivityManager; +import android.app.KeyguardManager; import android.app.NotificationManager; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -91,10 +93,8 @@ import javax.inject.Inject; public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpable { private static final String TAG = Util.logTag(VolumeDialogControllerImpl.class); - private static final int TOUCH_FEEDBACK_TIMEOUT_MS = 1000; private static final int DYNAMIC_STREAM_START_INDEX = 100; - private static final int VIBRATE_HINT_DURATION = 50; private static final AudioAttributes SONIFICIATION_VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) @@ -122,14 +122,16 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa private final PackageManager mPackageManager; private final MediaRouter2Manager mRouter2Manager; private final WakefulnessLifecycle mWakefulnessLifecycle; - private AudioManager mAudio; - private IAudioService mAudioService; + private final AudioManager mAudio; + private final IAudioService mAudioService; private final NotificationManager mNoMan; private final SettingObserver mObserver; private final Receiver mReceiver = new Receiver(); private final RingerModeObservers mRingerModeObservers; private final MediaSessions mMediaSessions; private final CaptioningManager mCaptioningManager; + private final KeyguardManager mKeyguardManager; + private final ActivityManager mActivityManager; protected C mCallbacks = new C(); private final State mState = new State(); protected final MediaSessionsCallbacks mMediaSessionsCallbacksW; @@ -141,9 +143,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa private long mLastToggledRingerOn; private boolean mDeviceInteractive = true; - private boolean mDestroyed; private VolumePolicy mVolumePolicy; - private boolean mShowDndTile = true; @GuardedBy("this") private UserActivityListener mUserActivityListener; @@ -176,7 +176,10 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa AccessibilityManager accessibilityManager, PackageManager packageManager, WakefulnessLifecycle wakefulnessLifecycle, - CaptioningManager captioningManager) { + CaptioningManager captioningManager, + KeyguardManager keyguardManager, + ActivityManager activityManager + ) { mContext = context.getApplicationContext(); mPackageManager = packageManager; mWakefulnessLifecycle = wakefulnessLifecycle; @@ -202,6 +205,9 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa mHasVibrator = mVibrator.hasVibrator(); mAudioService = iAudioService; mCaptioningManager = captioningManager; + mKeyguardManager = keyguardManager; + mActivityManager = activityManager; + boolean accessibilityVolumeStreamActive = accessibilityManager .isAccessibilityVolumeStreamActive(); @@ -247,7 +253,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa public void register() { setVolumeController(); setVolumePolicy(mVolumePolicy); - showDndTile(mShowDndTile); + showDndTile(); try { mMediaSessions.init(); } catch (SecurityException e) { @@ -272,10 +278,8 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa public void dump(PrintWriter pw, String[] args) { pw.println(VolumeDialogControllerImpl.class.getSimpleName() + " state:"); - pw.print(" mDestroyed: "); pw.println(mDestroyed); pw.print(" mVolumePolicy: "); pw.println(mVolumePolicy); pw.print(" mState: "); pw.println(mState.toString(4)); - pw.print(" mShowDndTile: "); pw.println(mShowDndTile); pw.print(" mHasVibrator: "); pw.println(mHasVibrator); synchronized (mMediaSessionsCallbacksW.mRemoteStreams) { pw.print(" mRemoteStreams: "); @@ -293,7 +297,6 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa } public void setUserActivityListener(UserActivityListener listener) { - if (mDestroyed) return; synchronized (this) { mUserActivityListener = listener; } @@ -304,7 +307,6 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa } public void getState() { - if (mDestroyed) return; mWorker.sendEmptyMessage(W.GET_STATE); } @@ -317,48 +319,39 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa } public void getCaptionsComponentState(boolean fromTooltip) { - if (mDestroyed) return; mWorker.obtainMessage(W.GET_CAPTIONS_COMPONENT_STATE, fromTooltip).sendToTarget(); } public void notifyVisible(boolean visible) { - if (mDestroyed) return; mWorker.obtainMessage(W.NOTIFY_VISIBLE, visible ? 1 : 0, 0).sendToTarget(); } public void userActivity() { - if (mDestroyed) return; mWorker.removeMessages(W.USER_ACTIVITY); mWorker.sendEmptyMessage(W.USER_ACTIVITY); } public void setRingerMode(int value, boolean external) { - if (mDestroyed) return; mWorker.obtainMessage(W.SET_RINGER_MODE, value, external ? 1 : 0).sendToTarget(); } public void setZenMode(int value) { - if (mDestroyed) return; mWorker.obtainMessage(W.SET_ZEN_MODE, value, 0).sendToTarget(); } public void setExitCondition(Condition condition) { - if (mDestroyed) return; mWorker.obtainMessage(W.SET_EXIT_CONDITION, condition).sendToTarget(); } public void setStreamMute(int stream, boolean mute) { - if (mDestroyed) return; mWorker.obtainMessage(W.SET_STREAM_MUTE, stream, mute ? 1 : 0).sendToTarget(); } public void setStreamVolume(int stream, int level) { - if (mDestroyed) return; mWorker.obtainMessage(W.SET_STREAM_VOLUME, stream, level).sendToTarget(); } public void setActiveStream(int stream) { - if (mDestroyed) return; mWorker.obtainMessage(W.SET_ACTIVE_STREAM, stream, 0).sendToTarget(); } @@ -392,7 +385,6 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa } private void onNotifyVisibleW(boolean visible) { - if (mDestroyed) return; mAudio.notifyVolumeControllerVisible(mVolumeController, visible); if (!visible) { if (updateActiveStreamW(-1)) { @@ -465,7 +457,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa mCallbacks.onStateChanged(mState); } if (showUI) { - mCallbacks.onShowRequested(Events.SHOW_REASON_VOLUME_CHANGED); + onShowRequestedW(Events.SHOW_REASON_VOLUME_CHANGED); } if (showVibrateHint) { mCallbacks.onShowVibrateHint(); @@ -645,6 +637,11 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa return true; } + private void onShowRequestedW(int reason) { + mCallbacks.onShowRequested(reason, mKeyguardManager.isKeyguardLocked(), + mActivityManager.getLockTaskModeState()); + } + private void onSetRingerModeW(int mode, boolean external) { if (external) { mAudio.setRingerMode(mode); @@ -687,9 +684,9 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa mCallbacks.onDismissRequested(reason); } - public void showDndTile(boolean visible) { + public void showDndTile() { if (D.BUG) Log.d(TAG, "showDndTile"); - DndTile.setVisible(mContext, visible); + DndTile.setVisible(mContext, true); } private final class VC extends IVolumeController.Stub { @@ -699,7 +696,6 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa public void displaySafeVolumeWarning(int flags) throws RemoteException { if (D.BUG) Log.d(TAG, "displaySafeVolumeWarning " + Util.audioManagerFlagsToString(flags)); - if (mDestroyed) return; mWorker.obtainMessage(W.SHOW_SAFETY_WARNING, flags, 0).sendToTarget(); } @@ -707,7 +703,6 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa public void volumeChanged(int streamType, int flags) throws RemoteException { if (D.BUG) Log.d(TAG, "volumeChanged " + AudioSystem.streamToString(streamType) + " " + Util.audioManagerFlagsToString(flags)); - if (mDestroyed) return; mWorker.obtainMessage(W.VOLUME_CHANGED, streamType, flags).sendToTarget(); } @@ -719,14 +714,12 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa @Override public void setLayoutDirection(int layoutDirection) throws RemoteException { if (D.BUG) Log.d(TAG, "setLayoutDirection"); - if (mDestroyed) return; mWorker.obtainMessage(W.LAYOUT_DIRECTION_CHANGED, layoutDirection, 0).sendToTarget(); } @Override public void dismiss() throws RemoteException { if (D.BUG) Log.d(TAG, "dismiss requested"); - if (mDestroyed) return; mWorker.obtainMessage(W.DISMISS_REQUESTED, Events.DISMISS_REASON_VOLUME_CONTROLLER, 0) .sendToTarget(); mWorker.sendEmptyMessage(W.DISMISS_REQUESTED); @@ -735,7 +728,6 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa @Override public void setA11yMode(int mode) { if (D.BUG) Log.d(TAG, "setA11yMode to " + mode); - if (mDestroyed) return; switch (mode) { case VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME: // "legacy" mode @@ -798,7 +790,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa } } - class C implements Callbacks { + static class C implements Callbacks { private final Map<Callbacks, Handler> mCallbackMap = new ConcurrentHashMap<>(); public void add(Callbacks callback, Handler handler) { @@ -811,12 +803,15 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa } @Override - public void onShowRequested(final int reason) { + public void onShowRequested( + final int reason, + final boolean keyguardLocked, + final int lockTaskModeState) { for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { entry.getValue().post(new Runnable() { @Override public void run() { - entry.getKey().onShowRequested(reason); + entry.getKey().onShowRequested(reason, keyguardLocked, lockTaskModeState); } }); } @@ -923,7 +918,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa @Override public void onAccessibilityModeChanged(Boolean showA11yStream) { - boolean show = showA11yStream == null ? false : showA11yStream; + boolean show = showA11yStream != null && showA11yStream; for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { entry.getValue().post(new Runnable() { @Override @@ -937,7 +932,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa @Override public void onCaptionComponentStateChanged( Boolean isComponentEnabled, Boolean fromTooltip) { - boolean componentEnabled = isComponentEnabled == null ? false : isComponentEnabled; + boolean componentEnabled = isComponentEnabled != null && isComponentEnabled; for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { entry.getValue().post( () -> entry.getKey().onCaptionComponentStateChanged( @@ -1183,7 +1178,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa mCallbacks.onStateChanged(mState); } if (showUI) { - mCallbacks.onShowRequested(Events.SHOW_REASON_REMOTE_VOLUME_CHANGED); + onShowRequestedW(Events.SHOW_REASON_REMOTE_VOLUME_CHANGED); } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index cf0d0238b3cc..e3cb2fa5a0e7 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -260,7 +260,7 @@ public class VolumeDialogImpl implements VolumeDialog, private State mState; private SafetyWarningDialog mSafetyWarning; private boolean mHovering = false; - private boolean mShowActiveStreamOnly; + private final boolean mShowActiveStreamOnly; private boolean mConfigChanged = false; private boolean mIsAnimatingDismiss = false; private boolean mHasSeenODICaptionsTooltip; @@ -327,7 +327,7 @@ public class VolumeDialogImpl implements VolumeDialog, } public void init(int windowType, Callback callback) { - initDialog(); + initDialog(mActivityManager.getLockTaskModeState()); mAccessibility.init(); @@ -394,7 +394,7 @@ public class VolumeDialogImpl implements VolumeDialog, Region.Op.UNION); } - private void initDialog() { + private void initDialog(int lockTaskModeState) { mDialog = new CustomDialog(mContext); initDimens(); @@ -589,7 +589,7 @@ public class VolumeDialogImpl implements VolumeDialog, updateRowsH(getActiveRow()); initRingerH(); - initSettingsH(); + initSettingsH(lockTaskModeState); initODICaptionsH(); } @@ -1034,12 +1034,11 @@ public class VolumeDialogImpl implements VolumeDialog, mIsRingerDrawerOpen = false; } - public void initSettingsH() { + private void initSettingsH(int lockTaskModeState) { if (mSettingsView != null) { mSettingsView.setVisibility( mDeviceProvisionedController.isCurrentUserSetup() && - mActivityManager.getLockTaskModeState() == LOCK_TASK_MODE_NONE ? - VISIBLE : GONE); + lockTaskModeState == LOCK_TASK_MODE_NONE ? VISIBLE : GONE); } if (mSettingsIcon != null) { mSettingsIcon.setOnClickListener(v -> { @@ -1292,7 +1291,7 @@ public class VolumeDialogImpl implements VolumeDialog, }; } - private void showH(int reason) { + private void showH(int reason, boolean keyguardLocked, int lockTaskModeState) { Trace.beginSection("VolumeDialogImpl#showH"); if (D.BUG) Log.d(TAG, "showH r=" + Events.SHOW_REASONS[reason]); mHandler.removeMessages(H.SHOW); @@ -1300,16 +1299,16 @@ public class VolumeDialogImpl implements VolumeDialog, rescheduleTimeoutH(); if (mConfigChanged) { - initDialog(); // resets mShowing to false + initDialog(lockTaskModeState); // resets mShowing to false mConfigurableTexts.update(); mConfigChanged = false; } - initSettingsH(); + initSettingsH(lockTaskModeState); mShowing = true; mIsAnimatingDismiss = false; mDialog.show(); - Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked()); + Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, keyguardLocked); mController.notifyVisible(true); mController.getCaptionsComponentState(false); checkODICaptionsTooltip(false); @@ -1810,14 +1809,6 @@ public class VolumeDialogImpl implements VolumeDialog, mContext, com.android.internal.R.attr.textColorOnAccent); row.sliderProgressSolid.setTintList(colorTint); - if (row.sliderBgIcon != null) { - row.sliderBgIcon.setTintList(colorTint); - } - - if (row.sliderBgSolid != null) { - row.sliderBgSolid.setTintList(bgTint); - } - if (row.sliderProgressIcon != null) { row.sliderProgressIcon.setTintList(bgTint); } @@ -2046,8 +2037,8 @@ public class VolumeDialogImpl implements VolumeDialog, private final VolumeDialogController.Callbacks mControllerCallbackH = new VolumeDialogController.Callbacks() { @Override - public void onShowRequested(int reason) { - showH(reason); + public void onShowRequested(int reason, boolean keyguardLocked, int lockTaskModeState) { + showH(reason, keyguardLocked, lockTaskModeState); } @Override @@ -2130,7 +2121,8 @@ public class VolumeDialogImpl implements VolumeDialog, @Override public void handleMessage(Message msg) { switch (msg.what) { - case SHOW: showH(msg.arg1); break; + case SHOW: showH(msg.arg1, VolumeDialogImpl.this.mKeyguard.isKeyguardLocked(), + VolumeDialogImpl.this.mActivityManager.getLockTaskModeState()); break; case DISMISS: dismissH(msg.arg1); break; case RECHECK: recheckH((VolumeRow) msg.obj); break; case RECHECK_ALL: recheckH(null); break; @@ -2152,7 +2144,7 @@ public class VolumeDialogImpl implements VolumeDialog, * within the bounds of the volume dialog, will fall through to the window below. */ @Override - public boolean dispatchTouchEvent(MotionEvent ev) { + public boolean dispatchTouchEvent(@NonNull MotionEvent ev) { rescheduleTimeoutH(); return super.dispatchTouchEvent(ev); } @@ -2176,7 +2168,7 @@ public class VolumeDialogImpl implements VolumeDialog, * volume dialog. */ @Override - public boolean onTouchEvent(MotionEvent event) { + public boolean onTouchEvent(@NonNull MotionEvent event) { if (mShowing) { if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { dismissH(Events.DISMISS_REASON_TOUCH_OUTSIDE); @@ -2265,8 +2257,6 @@ public class VolumeDialogImpl implements VolumeDialog, private View view; private TextView header; private ImageButton icon; - private Drawable sliderBgSolid; - private AlphaTintDrawableWrapper sliderBgIcon; private Drawable sliderProgressSolid; private AlphaTintDrawableWrapper sliderProgressIcon; private SeekBar slider; @@ -2280,7 +2270,6 @@ public class VolumeDialogImpl implements VolumeDialog, private int iconMuteRes; private boolean important; private boolean defaultStream; - private ColorStateList cachedTint; private int iconState; // from Events private ObjectAnimator anim; // slider progress animation for non-touch-related updates private int animTargetProgress; @@ -2295,9 +2284,6 @@ public class VolumeDialogImpl implements VolumeDialog, if (sliderProgressIcon != null) { sliderProgressIcon.setDrawable(view.getResources().getDrawable(iconRes, theme)); } - if (sliderBgIcon != null) { - sliderBgIcon.setDrawable(view.getResources().getDrawable(iconRes, theme)); - } } } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java index 542a53701529..83fc28189db5 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java @@ -271,6 +271,11 @@ public class BubblesManager implements Dumpable { mBubbles.onCurrentProfilesChanged(currentProfiles); } + @Override + public void onUserRemoved(int userId) { + mBubbles.onUserRemoved(userId); + } + }); mSysuiProxy = new Bubbles.SysuiProxy() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java index ec619bb5952a..aaf2188a2612 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java @@ -16,6 +16,8 @@ package com.android.systemui.volume; +import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -23,6 +25,8 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.ActivityManager; +import android.app.KeyguardManager; import android.app.NotificationManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -91,6 +95,10 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { private WakefulnessLifecycle mWakefullnessLifcycle; @Mock private CaptioningManager mCaptioningManager; + @Mock + private KeyguardManager mKeyguardManager; + @Mock + private ActivityManager mActivityManager; @Before @@ -112,7 +120,8 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { mVolumeController = new TestableVolumeDialogControllerImpl(mContext, mBroadcastDispatcher, mRingerModeTracker, mThreadFactory, mAudioManager, mNotificationManager, mVibrator, mIAudioService, mAccessibilityManager, - mPackageManager, mWakefullnessLifcycle, mCaptioningManager, mCallback); + mPackageManager, mWakefullnessLifcycle, mCaptioningManager, mKeyguardManager, + mActivityManager, mCallback); mVolumeController.setEnableDialogs(true, true); } @@ -129,7 +138,8 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { when(mWakefullnessLifcycle.getWakefulness()).thenReturn( WakefulnessLifecycle.WAKEFULNESS_AWAKE); mVolumeController.onVolumeChangedW(0, AudioManager.FLAG_SHOW_UI); - verify(mCallback, never()).onShowRequested(Events.SHOW_REASON_VOLUME_CHANGED); + verify(mCallback, never()).onShowRequested(Events.SHOW_REASON_VOLUME_CHANGED, false, + LOCK_TASK_MODE_NONE); } @Test @@ -138,7 +148,8 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { when(mWakefullnessLifcycle.getWakefulness()).thenReturn( WakefulnessLifecycle.WAKEFULNESS_AWAKE); mVolumeController.onVolumeChangedW(0, AudioManager.FLAG_SHOW_UI); - verify(mCallback, times(1)).onShowRequested(Events.SHOW_REASON_VOLUME_CHANGED); + verify(mCallback, times(1)).onShowRequested(Events.SHOW_REASON_VOLUME_CHANGED, false, + LOCK_TASK_MODE_NONE); } @Test @@ -151,7 +162,8 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { when(mWakefullnessLifcycle.getWakefulness()).thenReturn( WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP); mVolumeController.onVolumeChangedW(0, AudioManager.FLAG_SHOW_UI); - verify(mCallback, times(1)).onShowRequested(Events.SHOW_REASON_VOLUME_CHANGED); + verify(mCallback, times(1)).onShowRequested(Events.SHOW_REASON_VOLUME_CHANGED, false, + LOCK_TASK_MODE_NONE); } @Test @@ -188,10 +200,13 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { PackageManager packageManager, WakefulnessLifecycle wakefulnessLifecycle, CaptioningManager captioningManager, + KeyguardManager keyguardManager, + ActivityManager activityManager, C callback) { super(context, broadcastDispatcher, ringerModeTracker, theadFactory, audioManager, notificationManager, optionalVibrator, iAudioService, accessibilityManager, - packageManager, wakefulnessLifecycle, captioningManager); + packageManager, wakefulnessLifecycle, captioningManager, keyguardManager, + activityManager); mCallbacks = callback; ArgumentCaptor<WakefulnessLifecycle.Observer> observerCaptor = diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 238a4d37a872..7d4e27fa2580 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -66,6 +66,7 @@ import android.hardware.face.FaceManager; import android.os.Handler; import android.os.PowerManager; import android.os.UserHandle; +import android.os.UserManager; import android.service.dreams.IDreamManager; import android.service.notification.NotificationListenerService; import android.service.notification.ZenModeConfig; @@ -365,6 +366,7 @@ public class BubblesTest extends SysuiTestCase { mStatusBarService, mWindowManager, mWindowManagerShellWrapper, + mock(UserManager.class), mLauncherApps, mBubbleLogger, mTaskStackListener, diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java index dff89e0a5558..a6327b9daed5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java @@ -55,6 +55,7 @@ import android.hardware.display.AmbientDisplayConfiguration; import android.os.Handler; import android.os.PowerManager; import android.os.UserHandle; +import android.os.UserManager; import android.service.dreams.IDreamManager; import android.service.notification.NotificationListenerService; import android.service.notification.ZenModeConfig; @@ -330,6 +331,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mStatusBarService, mWindowManager, mWindowManagerShellWrapper, + mock(UserManager.class), mLauncherApps, mBubbleLogger, mTaskStackListener, diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java index 9ca6bb0af8d5..17e5778f7aab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java @@ -19,6 +19,7 @@ package com.android.systemui.wmshell; import android.content.Context; import android.content.pm.LauncherApps; import android.os.Handler; +import android.os.UserManager; import android.view.WindowManager; import com.android.internal.statusbar.IStatusBarService; @@ -53,6 +54,7 @@ public class TestableBubbleController extends BubbleController { IStatusBarService statusBarService, WindowManager windowManager, WindowManagerShellWrapper windowManagerShellWrapper, + UserManager userManager, LauncherApps launcherApps, BubbleLogger bubbleLogger, TaskStackListenerImpl taskStackListener, @@ -66,10 +68,10 @@ public class TestableBubbleController extends BubbleController { TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) { super(context, data, Runnable::run, floatingContentCoordinator, dataRepository, - statusBarService, windowManager, windowManagerShellWrapper, launcherApps, - bubbleLogger, taskStackListener, shellTaskOrganizer, positioner, displayController, - oneHandedOptional, dragAndDropController, shellMainExecutor, shellMainHandler, - new SyncExecutor(), taskViewTransitions, syncQueue); + statusBarService, windowManager, windowManagerShellWrapper, userManager, + launcherApps, bubbleLogger, taskStackListener, shellTaskOrganizer, positioner, + displayController, oneHandedOptional, dragAndDropController, shellMainExecutor, + shellMainHandler, new SyncExecutor(), taskViewTransitions, syncQueue); setInflateSynchronously(true); initialize(); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index aa510e7717eb..163df101620d 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -4409,6 +4409,14 @@ public class ActivityManagerService extends IActivityManager.Stub + " but does not exist in that user"); return; } + + // Policy: certain classes of app are not subject to user-invoked stop + if (getPackageManagerInternal().isPackageStateProtected(packageName, userId)) { + Slog.w(TAG, "Asked to stop " + packageName + "/u" + userId + + " but it is protected"); + return; + } + Slog.i(TAG, "Stopping app for user: " + packageName + "/" + userId); // A specific subset of the work done in forceStopPackageLocked(), because we are @@ -13598,13 +13606,8 @@ public class ActivityManagerService extends IActivityManager.Stub } if (brOptions.getIdForResponseEvent() > 0) { - // STOPSHIP (206518114): Temporarily check for PACKAGE_USAGE_STATS permission as - // well until the clients switch to using the new permission. - if (checkPermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS, - callingPid, callingUid) != PERMISSION_GRANTED) { - enforceUsageStatsPermission(callerPackage, callingUid, callingPid, - "recordResponseEventWhileInBackground()"); - } + enforcePermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS, + callingPid, callingUid, "recordResponseEventWhileInBackground"); } } diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 397a4420700e..2c2579f9e504 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -3561,6 +3561,9 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" Enable/disable rate limit on FGS notification deferral policy."); pw.println(" force-stop [--user <USER_ID> | all | current] <PACKAGE>"); pw.println(" Completely stop the given application package."); + pw.println(" stop-app [--user <USER_ID> | all | current] <PACKAGE>"); + pw.println(" Stop an app and all of its services. Unlike `force-stop` this does"); + pw.println(" not cancel the app's scheduled alarms and jobs."); pw.println(" crash [--user <USER_ID>] <PACKAGE|PID>"); pw.println(" Induce a VM crash in the specified package or process"); pw.println(" kill [--user <USER_ID> | all | current] <PACKAGE>"); diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java index 29b122d56ac5..aabe8a163df7 100644 --- a/services/core/java/com/android/server/pm/DexOptHelper.java +++ b/services/core/java/com/android/server/pm/DexOptHelper.java @@ -18,6 +18,7 @@ package com.android.server.pm; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; +import static com.android.server.pm.ApexManager.ActiveApexInfo; import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT; import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; @@ -46,6 +47,7 @@ import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.provider.DeviceConfig; +import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import android.util.Slog; @@ -62,12 +64,16 @@ import com.android.server.pm.pkg.PackageStateInternal; import dalvik.system.DexFile; import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; @@ -315,10 +321,11 @@ final class DexOptHelper { // The default is "true". if (!"false".equals(DeviceConfig.getProperty("runtime", "dexopt_system_ui_on_boot"))) { - // System UI is important to user experience, so we check it on every boot. It may need - // to be re-compiled after a mainline update or an OTA. - // TODO(b/227310505): Only do this after a mainline update or an OTA. - checkAndDexOptSystemUi(); + // System UI is important to user experience, so we check it after a mainline update or + // an OTA. It may need to be re-compiled in these cases. + if (hasBcpApexesChanged() || mPm.isDeviceUpgrading()) { + checkAndDexOptSystemUi(); + } } // We need to re-extract after an OTA. @@ -745,4 +752,42 @@ final class DexOptHelper { /*package*/ void controlDexOptBlocking(boolean block) { mPm.mPackageDexOptimizer.controlDexOptBlocking(block); } + + /** + * Returns the module names of the APEXes that contribute to bootclasspath. + */ + private static List<String> getBcpApexes() { + String bcp = System.getenv("BOOTCLASSPATH"); + if (TextUtils.isEmpty(bcp)) { + Log.e(TAG, "Unable to get BOOTCLASSPATH"); + return List.of(); + } + + ArrayList<String> bcpApexes = new ArrayList<>(); + for (String pathStr : bcp.split(":")) { + Path path = Paths.get(pathStr); + // Check if the path is in the format of `/apex/<apex-module-name>/...` and extract the + // apex module name from the path. + if (path.getNameCount() >= 2 && path.getName(0).toString().equals("apex")) { + bcpApexes.add(path.getName(1).toString()); + } + } + + return bcpApexes; + } + + /** + * Returns true of any of the APEXes that contribute to bootclasspath has changed during this + * boot. + */ + private static boolean hasBcpApexesChanged() { + Set<String> bcpApexes = new HashSet<>(getBcpApexes()); + ApexManager apexManager = ApexManager.getInstance(); + for (ActiveApexInfo apexInfo : apexManager.getActiveApexInfos()) { + if (bcpApexes.contains(apexInfo.apexModuleName) && apexInfo.activeApexChanged) { + return true; + } + } + return false; + } } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index bcdf4291ed41..fcdab88a4c9b 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -4207,7 +4207,9 @@ public class UserManagerService extends IUserManager.Stub { writeUserListLP(); } updateUserIds(); - mPm.onNewUserCreated(preCreatedUser.id, /* convertedFromPreCreated= */ true); + Binder.withCleanCallingIdentity(() -> { + mPm.onNewUserCreated(preCreatedUser.id, /* convertedFromPreCreated= */ true); + }); dispatchUserAdded(preCreatedUser, token); return preCreatedUser; } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 4075cddc302c..3c13abf4403c 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -72,8 +72,8 @@ import android.os.Looper; import android.os.Message; import android.os.ParcelDuration; import android.os.PowerManager; +import android.os.PowerManager.GoToSleepReason; import android.os.PowerManager.ServiceType; -import android.os.PowerManager.WakeData; import android.os.PowerManager.WakeReason; import android.os.PowerManagerInternal; import android.os.PowerSaveState; @@ -352,7 +352,7 @@ public final class PowerManagerService extends SystemService // Last reason the device went to sleep. private @WakeReason int mLastGlobalWakeReason; - private int mLastGlobalSleepReason; + private @GoToSleepReason int mLastGlobalSleepReason; // Timestamp of last time power boost interaction was sent. private long mLastInteractivePowerHintTime; @@ -6350,20 +6350,26 @@ public final class PowerManagerService extends SystemService } } + @GoToSleepReason private int getLastSleepReasonInternal() { synchronized (mLock) { return mLastGlobalSleepReason; } } - @VisibleForTesting private PowerManager.WakeData getLastWakeupInternal() { synchronized (mLock) { - return new WakeData(mLastGlobalWakeTime, mLastGlobalWakeReason, + return new PowerManager.WakeData(mLastGlobalWakeTime, mLastGlobalWakeReason, mLastGlobalWakeTime - mLastGlobalSleepTime); } } + private PowerManager.SleepData getLastGoToSleepInternal() { + synchronized (mLock) { + return new PowerManager.SleepData(mLastGlobalSleepTime, mLastGlobalSleepReason); + } + } + /** * If the user presses power while the proximity sensor is enabled and keeping * the screen off, then turn the screen back on by telling display manager to @@ -6528,11 +6534,16 @@ public final class PowerManagerService extends SystemService } @Override - public WakeData getLastWakeup() { + public PowerManager.WakeData getLastWakeup() { return getLastWakeupInternal(); } @Override + public PowerManager.SleepData getLastGoToSleep() { + return getLastGoToSleepInternal(); + } + + @Override public boolean interceptPowerKeyDown(KeyEvent event) { return interceptPowerKeyDownInternal(event); } diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java index 3e364314e10f..78b1c20ac4b2 100644 --- a/services/core/java/com/android/server/vibrator/Vibration.java +++ b/services/core/java/com/android/server/vibrator/Vibration.java @@ -71,8 +71,8 @@ final class Vibration { IGNORED_SUPERSEDED, } - /** Start time in CLOCK_BOOTTIME base. */ - public final long startTime; + /** Start time using {@link SystemClock#uptimeMillis()}, for calculations. */ + public final long startUptimeMillis; public final VibrationAttributes attrs; public final long id; public final int uid; @@ -94,11 +94,14 @@ final class Vibration { /** * Start/end times in unix epoch time. Only to be used for debugging purposes and to correlate - * with other system events, any duration calculations should be done use {@link #startTime} so - * as not to be affected by discontinuities created by RTC adjustments. + * with other system events, any duration calculations should be done use + * {@link #startUptimeMillis} so as not to be affected by discontinuities created by RTC + * adjustments. */ private final long mStartTimeDebug; private long mEndTimeDebug; + /** End time using {@link SystemClock#uptimeMillis()}, for calculations. */ + private long mEndUptimeMillis; private Status mStatus; /** A {@link CountDownLatch} to enable waiting for completion. */ @@ -109,7 +112,7 @@ final class Vibration { this.token = token; this.mEffect = effect; this.id = id; - this.startTime = SystemClock.elapsedRealtime(); + this.startUptimeMillis = SystemClock.uptimeMillis(); this.attrs = attrs; this.uid = uid; this.opPkg = opPkg; @@ -131,6 +134,7 @@ final class Vibration { return; } mStatus = status; + mEndUptimeMillis = SystemClock.uptimeMillis(); mEndTimeDebug = System.currentTimeMillis(); mCompletionLatch.countDown(); } @@ -225,15 +229,17 @@ final class Vibration { /** Return {@link Vibration.DebugInfo} with read-only debug information about this vibration. */ public Vibration.DebugInfo getDebugInfo() { + long durationMs = hasEnded() ? mEndUptimeMillis - startUptimeMillis : -1; return new Vibration.DebugInfo( - mStartTimeDebug, mEndTimeDebug, mEffect, mOriginalEffect, /* scale= */ 0, attrs, - uid, opPkg, reason, mStatus); + mStartTimeDebug, mEndTimeDebug, durationMs, mEffect, mOriginalEffect, + /* scale= */ 0, attrs, uid, opPkg, reason, mStatus); } /** Debug information about vibrations. */ static final class DebugInfo { private final long mStartTimeDebug; private final long mEndTimeDebug; + private final long mDurationMs; private final CombinedVibration mEffect; private final CombinedVibration mOriginalEffect; private final float mScale; @@ -243,11 +249,12 @@ final class Vibration { private final String mReason; private final Status mStatus; - DebugInfo(long startTimeDebug, long endTimeDebug, CombinedVibration effect, - CombinedVibration originalEffect, float scale, VibrationAttributes attrs, - int uid, String opPkg, String reason, Status status) { + DebugInfo(long startTimeDebug, long endTimeDebug, long durationMs, + CombinedVibration effect, CombinedVibration originalEffect, float scale, + VibrationAttributes attrs, int uid, String opPkg, String reason, Status status) { mStartTimeDebug = startTimeDebug; mEndTimeDebug = endTimeDebug; + mDurationMs = durationMs; mEffect = effect; mOriginalEffect = originalEffect; mScale = scale; @@ -266,6 +273,8 @@ final class Vibration { .append(", endTime: ") .append(mEndTimeDebug == 0 ? null : DEBUG_DATE_FORMAT.format(new Date(mEndTimeDebug))) + .append(", durationMs: ") + .append(mDurationMs) .append(", status: ") .append(mStatus.name().toLowerCase()) .append(", effect: ") @@ -290,6 +299,7 @@ final class Vibration { final long token = proto.start(fieldId); proto.write(VibrationProto.START_TIME, mStartTimeDebug); proto.write(VibrationProto.END_TIME, mEndTimeDebug); + proto.write(VibrationProto.DURATION_MS, mDurationMs); proto.write(VibrationProto.STATUS, mStatus.ordinal()); final long attrsToken = proto.start(VibrationProto.ATTRIBUTES); diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java index ac635a0746c5..f9ffd92a7a1c 100644 --- a/services/core/java/com/android/server/vibrator/VibrationSettings.java +++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java @@ -52,6 +52,7 @@ import android.os.Vibrator; import android.os.Vibrator.VibrationIntensity; import android.os.vibrator.VibrationConfig; import android.provider.Settings; +import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import android.util.proto.ProtoOutputStream; @@ -121,6 +122,19 @@ final class VibrationSettings { USAGE_PHYSICAL_EMULATION, USAGE_HARDWARE_FEEDBACK)); + /** + * Set of reasons for {@link PowerManager} going to sleep events that allows vibrations to + * continue running. + * + * <p>Some examples are timeout and inattentive, which indicates automatic screen off events. + * When a vibration is playing during one of these screen off events then it will not be + * cancelled by the service. + */ + private static final Set<Integer> POWER_MANAGER_SLEEP_REASON_ALLOWLIST = new HashSet<>( + Arrays.asList( + PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE, + PowerManager.GO_TO_SLEEP_REASON_TIMEOUT)); + private static final IntentFilter USER_SWITCHED_INTENT_FILTER = new IntentFilter(Intent.ACTION_USER_SWITCHED); private static final IntentFilter INTERNAL_RINGER_MODE_CHANGED_INTENT_FILTER = @@ -135,7 +149,8 @@ final class VibrationSettings { private final Object mLock = new Object(); private final Context mContext; private final String mSystemUiPackage; - private final SettingsContentObserver mSettingObserver; + @VisibleForTesting + final SettingsContentObserver mSettingObserver; @VisibleForTesting final UidObserver mUidObserver; @VisibleForTesting @@ -150,6 +165,9 @@ final class VibrationSettings { @GuardedBy("mLock") @Nullable private AudioManager mAudioManager; + @GuardedBy("mLock") + @Nullable + private PowerManagerInternal mPowerManagerInternal; @GuardedBy("mLock") private boolean mVibrateInputDevices; @@ -199,10 +217,16 @@ final class VibrationSettings { } public void onSystemReady() { + PowerManagerInternal pm = LocalServices.getService(PowerManagerInternal.class); + AudioManager am = mContext.getSystemService(AudioManager.class); + int ringerMode = am.getRingerModeInternal(); + synchronized (mLock) { - mAudioManager = mContext.getSystemService(AudioManager.class); - mRingerMode = mAudioManager.getRingerModeInternal(); + mPowerManagerInternal = pm; + mAudioManager = am; + mRingerMode = ringerMode; } + try { ActivityManager.getService().registerUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE, @@ -211,7 +235,6 @@ final class VibrationSettings { // ignored; both services live in system_server } - PowerManagerInternal pm = LocalServices.getService(PowerManagerInternal.class); pm.registerLowPowerModeObserver( new PowerManagerInternal.LowPowerModeListener() { @Override @@ -381,7 +404,27 @@ final class VibrationSettings { * @return true if the vibration should be cancelled when the screen goes off, false otherwise. */ public boolean shouldCancelVibrationOnScreenOff(int uid, String opPkg, - @VibrationAttributes.Usage int usage) { + @VibrationAttributes.Usage int usage, long vibrationStartUptimeMillis) { + PowerManagerInternal pm; + synchronized (mLock) { + pm = mPowerManagerInternal; + } + if (pm != null) { + // The SleepData from PowerManager may refer to a more recent sleep than the broadcast + // that triggered this method call. That's ok because only automatic sleeps would be + // ignored here and not cancel a vibration, and those are usually triggered by timeout + // or inactivity, so it's unlikely that it will override a more active goToSleep reason. + PowerManager.SleepData sleepData = pm.getLastGoToSleep(); + if ((sleepData.goToSleepUptimeMillis < vibrationStartUptimeMillis) + || POWER_MANAGER_SLEEP_REASON_ALLOWLIST.contains(sleepData.goToSleepReason)) { + // Ignore screen off events triggered before the vibration started, and all + // automatic "go to sleep" events from allowlist. + Slog.d(TAG, "Ignoring screen off event triggered at uptime " + + sleepData.goToSleepUptimeMillis + " for reason " + + PowerManager.sleepReasonToString(sleepData.goToSleepReason)); + return false; + } + } if (!SYSTEM_VIBRATION_SCREEN_OFF_USAGE_ALLOWLIST.contains(usage)) { // Usages not allowed even for system vibrations should always be cancelled. return true; @@ -628,7 +671,8 @@ final class VibrationSettings { } /** Implementation of {@link ContentObserver} to be registered to a setting {@link Uri}. */ - private final class SettingsContentObserver extends ContentObserver { + @VisibleForTesting + final class SettingsContentObserver extends ContentObserver { SettingsContentObserver(Handler handler) { super(handler); } diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index d7341cb37685..f0911ca62027 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -47,6 +47,7 @@ import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.ShellCallback; import android.os.ShellCommand; +import android.os.SystemClock; import android.os.Trace; import android.os.VibrationAttributes; import android.os.VibrationEffect; @@ -405,7 +406,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) { // Force update of user settings before checking if this vibration effect should // be ignored or scaled. - mVibrationSettings.update(); + mVibrationSettings.mSettingObserver.onChange(false); } synchronized (mLock) { @@ -1103,7 +1104,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } Vibration vib = conductor.getVibration(); return mVibrationSettings.shouldCancelVibrationOnScreenOff( - vib.uid, vib.opPkg, vib.attrs.getUsage()); + vib.uid, vib.opPkg, vib.attrs.getUsage(), vib.startUptimeMillis); } @GuardedBy("mLock") @@ -1308,13 +1309,17 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { public final ExternalVibration externalVibration; public int scale; + private final long mStartUptimeMillis; private final long mStartTimeDebug; + + private long mEndUptimeMillis; private long mEndTimeDebug; private Vibration.Status mStatus; private ExternalVibrationHolder(ExternalVibration externalVibration) { this.externalVibration = externalVibration; this.scale = IExternalVibratorService.SCALE_NONE; + mStartUptimeMillis = SystemClock.uptimeMillis(); mStartTimeDebug = System.currentTimeMillis(); mStatus = Vibration.Status.RUNNING; } @@ -1325,6 +1330,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { return; } mStatus = status; + mEndUptimeMillis = SystemClock.uptimeMillis(); mEndTimeDebug = System.currentTimeMillis(); } @@ -1341,11 +1347,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } public Vibration.DebugInfo getDebugInfo() { + long durationMs = mEndUptimeMillis == 0 ? -1 : mEndUptimeMillis - mStartUptimeMillis; return new Vibration.DebugInfo( - mStartTimeDebug, mEndTimeDebug, /* effect= */ null, /* originalEffect= */ null, - scale, externalVibration.getVibrationAttributes(), - externalVibration.getUid(), externalVibration.getPackage(), - /* reason= */ null, mStatus); + mStartTimeDebug, mEndTimeDebug, durationMs, + /* effect= */ null, /* originalEffect= */ null, scale, + externalVibration.getVibrationAttributes(), externalVibration.getUid(), + externalVibration.getPackage(), /* reason= */ null, mStatus); } } diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java index 8167b44ee59d..758a56f3d2ad 100644 --- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java @@ -360,67 +360,6 @@ public class SystemConfigTest { .containsExactly("android.permission.BAR"); } - @Test - public void pruneVendorApexPrivappAllowlists_removeVendor() - throws Exception { - File apexDir = createTempSubfolder("apex"); - - // Read non-vendor apex permission allowlists - final String allowlistNonVendorContents = - "<privapp-permissions package=\"com.android.apk_in_non_vendor_apex\">" - + "<permission name=\"android.permission.FOO\"/>" - + "<deny-permission name=\"android.permission.BAR\"/>" - + "</privapp-permissions>"; - File nonVendorPermDir = - createTempSubfolder("apex/com.android.non_vendor/etc/permissions"); - File nonVendorPermissionFile = - createTempFile(nonVendorPermDir, "permissions.xml", allowlistNonVendorContents); - XmlPullParser nonVendorParser = readXmlUntilStartTag(nonVendorPermissionFile); - mSysConfig.readApexPrivAppPermissions(nonVendorParser, nonVendorPermissionFile, - apexDir.toPath()); - - // Read vendor apex permission allowlists - final String allowlistVendorContents = - "<privapp-permissions package=\"com.android.apk_in_vendor_apex\">" - + "<permission name=\"android.permission.BAZ\"/>" - + "<deny-permission name=\"android.permission.BAT\"/>" - + "</privapp-permissions>"; - File vendorPermissionFile = - createTempFile(createTempSubfolder("apex/com.android.vendor/etc/permissions"), - "permissions.xml", allowlistNonVendorContents); - XmlPullParser vendorParser = readXmlUntilStartTag(vendorPermissionFile); - mSysConfig.readApexPrivAppPermissions(vendorParser, vendorPermissionFile, - apexDir.toPath()); - - // Read allowed vendor apex list - final String allowedVendorContents = - "<config>\n" - + " <allowed-vendor-apex package=\"com.android.vendor\" " - + "installerPackage=\"com.installer\" />\n" - + "</config>"; - final File allowedVendorFolder = createTempSubfolder("folder"); - createTempFile(allowedVendorFolder, "vendor-apex-allowlist.xml", allowedVendorContents); - readPermissions(allowedVendorFolder, /* Grant all permission flags */ ~0); - - // Finally, prune non-vendor allowlists. - // There is no guarantee in which order the above reads will be done, however pruning - // will always happen last. - mSysConfig.pruneVendorApexPrivappAllowlists(); - - assertThat(mSysConfig.getApexPrivAppPermissions("com.android.non_vendor", - "com.android.apk_in_non_vendor_apex")) - .containsExactly("android.permission.FOO"); - assertThat(mSysConfig.getApexPrivAppDenyPermissions("com.android.non_vendor", - "com.android.apk_in_non_vendor_apex")) - .containsExactly("android.permission.BAR"); - assertThat(mSysConfig.getApexPrivAppPermissions("com.android.vendor", - "com.android.apk_in_vendor_apex")) - .isNull(); - assertThat(mSysConfig.getApexPrivAppDenyPermissions("com.android.vendor", - "com.android.apk_in_vendor_apex")) - .isNull(); - } - /** * Tests that readPermissions works correctly for a library with on-bootclasspath-before * and on-bootclasspath-since. diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java index b907c62be6fb..64950aa69e3b 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java @@ -277,6 +277,6 @@ public class VibrationScalerTest { Settings.System.putIntForUser( mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT); // FakeSettingsProvider don't support testing triggering ContentObserver yet. - mVibrationSettings.update(); + mVibrationSettings.mSettingObserver.onChange(false); } } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java index 0a50e790215f..b214dd0ad2ba 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java @@ -54,6 +54,7 @@ import android.content.Intent; import android.content.pm.PackageManagerInternal; import android.media.AudioManager; import android.os.Handler; +import android.os.PowerManager; import android.os.PowerManagerInternal; import android.os.PowerSaveState; import android.os.Process; @@ -146,6 +147,8 @@ public class VibrationSettingsTest { mVibrationSettings = new VibrationSettings(mContextSpy, new Handler(mTestLooper.getLooper()), mVibrationConfigMock); + mockGoToSleep(/* goToSleepTime= */ 0, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT); + // Simulate System defaults. setUserSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, 1); setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0); @@ -164,18 +167,10 @@ public class VibrationSettingsTest { public void addListener_settingsChangeTriggerListener() { mVibrationSettings.addListener(mListenerMock); - setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1); - setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); - setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0); - setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF); - setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF); - setUserSetting(Settings.System.MEDIA_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF); - setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF); - setUserSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, 0); - setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); - setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); + mVibrationSettings.mSettingObserver.onChange(false); + mVibrationSettings.mSettingObserver.onChange(false); - verify(mListenerMock, times(10)).onChange(); + verify(mListenerMock, times(2)).onChange(); } @Test @@ -479,50 +474,112 @@ public class VibrationSettingsTest { } @Test - public void shouldCancelVibrationOnScreenOff_withNonSystemPackageAndUid_returnsAlwaysTrue() { + public void shouldCancelVibrationOnScreenOff_withEventBeforeVibration_returnsAlwaysFalse() { + long vibrateStartTime = 100; + mockGoToSleep(vibrateStartTime - 10, PowerManager.GO_TO_SLEEP_REASON_APPLICATION); + + for (int usage : ALL_USAGES) { + // Non-system vibration + assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff( + UID, "some.app", usage, vibrateStartTime)); + // Vibration with UID zero + assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff( + /* uid= */ 0, "", usage, vibrateStartTime)); + // System vibration + assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff( + Process.SYSTEM_UID, "", usage, vibrateStartTime)); + // SysUI vibration + assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff( + UID, SYSUI_PACKAGE_NAME, usage, vibrateStartTime)); + } + } + + @Test + public void shouldCancelVibrationOnScreenOff_withSleepReasonInAllowlist_returnsAlwaysFalse() { + long vibrateStartTime = 100; + int[] allowedSleepReasons = new int[] { + PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, + PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE, + }; + + for (int sleepReason : allowedSleepReasons) { + mockGoToSleep(vibrateStartTime + 10, sleepReason); + + for (int usage : ALL_USAGES) { + // Non-system vibration + assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff( + UID, "some.app", usage, vibrateStartTime)); + // Vibration with UID zero + assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff( + /* uid= */ 0, "", usage, vibrateStartTime)); + // System vibration + assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff( + Process.SYSTEM_UID, "", usage, vibrateStartTime)); + // SysUI vibration + assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff( + UID, SYSUI_PACKAGE_NAME, usage, vibrateStartTime)); + } + } + } + + @Test + public void shouldCancelVibrationOnScreenOff_withNonSystem_returnsTrueIfReasonNotInAllowlist() { + long vibrateStartTime = 100; + mockGoToSleep(vibrateStartTime + 10, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON); + for (int usage : ALL_USAGES) { - assertTrue(mVibrationSettings.shouldCancelVibrationOnScreenOff(UID, "some.app", usage)); + assertTrue(mVibrationSettings.shouldCancelVibrationOnScreenOff( + UID, "some.app", usage, vibrateStartTime)); } } @Test public void shouldCancelVibrationOnScreenOff_withUidZero_returnsFalseForTouchAndHardware() { + long vibrateStartTime = 100; + mockGoToSleep(vibrateStartTime + 10, PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN); + for (int usage : ALL_USAGES) { if (usage == USAGE_TOUCH || usage == USAGE_HARDWARE_FEEDBACK || usage == USAGE_PHYSICAL_EMULATION) { assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff( - /* uid= */ 0, "", usage)); + /* uid= */ 0, "", usage, vibrateStartTime)); } else { assertTrue(mVibrationSettings.shouldCancelVibrationOnScreenOff( - /* uid= */ 0, "", usage)); + /* uid= */ 0, "", usage, vibrateStartTime)); } } } @Test public void shouldCancelVibrationOnScreenOff_withSystemUid_returnsFalseForTouchAndHardware() { + long vibrateStartTime = 100; + mockGoToSleep(vibrateStartTime + 10, PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD); + for (int usage : ALL_USAGES) { if (usage == USAGE_TOUCH || usage == USAGE_HARDWARE_FEEDBACK || usage == USAGE_PHYSICAL_EMULATION) { assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff( - Process.SYSTEM_UID, "", usage)); + Process.SYSTEM_UID, "", usage, vibrateStartTime)); } else { assertTrue(mVibrationSettings.shouldCancelVibrationOnScreenOff( - Process.SYSTEM_UID, "", usage)); + Process.SYSTEM_UID, "", usage, vibrateStartTime)); } } } @Test - public void shouldCancelVibrationOnScreenOff_withSysUi_returnsFalseForTouchAndHardware() { + public void shouldCancelVibrationOnScreenOff_withSysUiPkg_returnsFalseForTouchAndHardware() { + long vibrateStartTime = 100; + mockGoToSleep(vibrateStartTime + 10, PowerManager.GO_TO_SLEEP_REASON_HDMI); + for (int usage : ALL_USAGES) { if (usage == USAGE_TOUCH || usage == USAGE_HARDWARE_FEEDBACK || usage == USAGE_PHYSICAL_EMULATION) { assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff( - UID, SYSUI_PACKAGE_NAME, usage)); + UID, SYSUI_PACKAGE_NAME, usage, vibrateStartTime)); } else { assertTrue(mVibrationSettings.shouldCancelVibrationOnScreenOff( - UID, SYSUI_PACKAGE_NAME, usage)); + UID, SYSUI_PACKAGE_NAME, usage, vibrateStartTime)); } } } @@ -581,7 +638,6 @@ public class VibrationSettingsTest { public void getCurrentIntensity_noHardwareFeedbackValueUsesHapticFeedbackValue() { setDefaultIntensity(USAGE_HARDWARE_FEEDBACK, VIBRATION_INTENSITY_MEDIUM); setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); - mVibrationSettings.update(); assertEquals(VIBRATION_INTENSITY_OFF, mVibrationSettings.getCurrentIntensity(USAGE_TOUCH)); // If haptic feedback is off, fallback to default value. assertEquals(VIBRATION_INTENSITY_MEDIUM, @@ -590,7 +646,6 @@ public class VibrationSettingsTest { mVibrationSettings.getCurrentIntensity(USAGE_PHYSICAL_EMULATION)); setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH); - mVibrationSettings.update(); assertEquals(VIBRATION_INTENSITY_HIGH, mVibrationSettings.getCurrentIntensity(USAGE_TOUCH)); // If haptic feedback is on, fallback to that value. @@ -648,19 +703,25 @@ public class VibrationSettingsTest { Settings.System.putStringForUser( mContextSpy.getContentResolver(), settingName, null, UserHandle.USER_CURRENT); // FakeSettingsProvider doesn't support testing triggering ContentObserver yet. - mVibrationSettings.update(); + mVibrationSettings.mSettingObserver.onChange(false); } private void setUserSetting(String settingName, int value) { Settings.System.putIntForUser( mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT); // FakeSettingsProvider doesn't support testing triggering ContentObserver yet. - mVibrationSettings.update(); + mVibrationSettings.mSettingObserver.onChange(false); } private void setRingerMode(int ringerMode) { mAudioManager.setRingerModeInternal(ringerMode); assertEquals(ringerMode, mAudioManager.getRingerModeInternal()); - mVibrationSettings.update(); + mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy, + new Intent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)); + } + + private void mockGoToSleep(long sleepTime, int reason) { + when(mPowerManagerInternalMock.getLastGoToSleep()).thenReturn( + new PowerManager.SleepData(sleepTime, reason)); } } diff --git a/services/usage/OWNERS b/services/usage/OWNERS index 6c20e746f35e..26d9b10c7907 100644 --- a/services/usage/OWNERS +++ b/services/usage/OWNERS @@ -3,4 +3,5 @@ varunshah@google.com huiyu@google.com yamasani@google.com -per-file *StorageStats* = file:/core/java/android/os/storage/OWNERS
\ No newline at end of file +per-file *StorageStats* = file:/core/java/android/os/storage/OWNERS +per-file *Broadcast* = sudheersai@google.com
\ No newline at end of file diff --git a/services/usage/java/com/android/server/usage/BroadcastEvent.java b/services/usage/java/com/android/server/usage/BroadcastEvent.java index ceb79c107d30..e56a541078e6 100644 --- a/services/usage/java/com/android/server/usage/BroadcastEvent.java +++ b/services/usage/java/com/android/server/usage/BroadcastEvent.java @@ -19,6 +19,7 @@ package com.android.server.usage; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.util.LongArrayQueue; import java.util.Objects; @@ -30,6 +31,7 @@ class BroadcastEvent { private String mTargetPackage; private int mTargetUserId; private long mIdForResponseEvent; + private final LongArrayQueue mTimestampsMs; BroadcastEvent(int sourceUid, @NonNull String targetPackage, @UserIdInt int targetUserId, long idForResponseEvent) { @@ -37,6 +39,7 @@ class BroadcastEvent { mTargetPackage = targetPackage; mTargetUserId = targetUserId; mIdForResponseEvent = idForResponseEvent; + mTimestampsMs = new LongArrayQueue(); } public int getSourceUid() { @@ -55,6 +58,14 @@ class BroadcastEvent { return mIdForResponseEvent; } + public LongArrayQueue getTimestampsMs() { + return mTimestampsMs; + } + + public void addTimestampMs(long timestampMs) { + mTimestampsMs.addLast(timestampMs); + } + @Override public boolean equals(@Nullable Object obj) { if (this == obj) { diff --git a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java index 76d2fe7917fc..7e3990d95915 100644 --- a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java +++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java @@ -24,8 +24,10 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager.ProcessState; import android.app.usage.BroadcastResponseStats; +import android.os.SystemClock; import android.os.UserHandle; -import android.util.LongSparseArray; +import android.util.ArraySet; +import android.util.LongArrayQueue; import android.util.Slog; import android.util.SparseArray; @@ -89,14 +91,14 @@ class BroadcastResponseStatsTracker { return; } synchronized (mLock) { - final LongSparseArray<BroadcastEvent> broadcastEvents = + final ArraySet<BroadcastEvent> broadcastEvents = getOrCreateBroadcastEventsLocked(targetPackage, targetUser); - final BroadcastEvent broadcastEvent = new BroadcastEvent( + final BroadcastEvent broadcastEvent = getOrCreateBroadcastEvent(broadcastEvents, sourceUid, targetPackage, targetUser.getIdentifier(), idForResponseEvent); - broadcastEvents.append(timestampMs, broadcastEvent); - final BroadcastResponseStats responseStats = - getOrCreateBroadcastResponseStats(broadcastEvent); - responseStats.incrementBroadcastsDispatchedCount(1); + broadcastEvent.addTimestampMs(timestampMs); + + // Delete any old broadcast event related data so that we don't keep accumulating them. + recordAndPruneOldBroadcastDispatchTimestamps(broadcastEvent); } } @@ -120,44 +122,87 @@ class BroadcastResponseStatsTracker { @NonNull String packageName, UserHandle user, @ElapsedRealtimeLong long timestampMs) { mLogger.logNotificationEvent(event, packageName, user, timestampMs); synchronized (mLock) { - final LongSparseArray<BroadcastEvent> broadcastEvents = + final ArraySet<BroadcastEvent> broadcastEvents = getBroadcastEventsLocked(packageName, user); if (broadcastEvents == null) { return; } - // TODO (206518114): Add LongSparseArray.removeAtRange() + final long broadcastResponseWindowDurationMs = + mAppStandby.getBroadcastResponseWindowDurationMs(); + final long broadcastsSessionWithResponseDurationMs = + mAppStandby.getBroadcastSessionsWithResponseDurationMs(); + final boolean recordAllBroadcastsSessionsWithinResponseWindow = + mAppStandby.shouldNoteResponseEventForAllBroadcastSessions(); for (int i = broadcastEvents.size() - 1; i >= 0; --i) { - final long dispatchTimestampMs = broadcastEvents.keyAt(i); - final long elapsedDurationMs = timestampMs - dispatchTimestampMs; - if (elapsedDurationMs <= 0) { - continue; - } - if (dispatchTimestampMs >= timestampMs) { - continue; - } - if (elapsedDurationMs <= mAppStandby.getBroadcastResponseWindowDurationMs()) { - final BroadcastEvent broadcastEvent = broadcastEvents.valueAt(i); - final BroadcastResponseStats responseStats = - getBroadcastResponseStats(broadcastEvent); - if (responseStats == null) { - continue; - } - switch (event) { - case NOTIFICATION_EVENT_TYPE_POSTED: - responseStats.incrementNotificationsPostedCount(1); - break; - case NOTIFICATION_EVENT_TYPE_UPDATED: - responseStats.incrementNotificationsUpdatedCount(1); - break; - case NOTIFICATION_EVENT_TYPE_CANCELLED: - responseStats.incrementNotificationsCancelledCount(1); + final BroadcastEvent broadcastEvent = broadcastEvents.valueAt(i); + recordAndPruneOldBroadcastDispatchTimestamps(broadcastEvent); + + final LongArrayQueue dispatchTimestampsMs = broadcastEvent.getTimestampsMs(); + long broadcastsSessionEndTimestampMs = 0; + // We only need to look at the broadcast events that occurred before + // this notification related event. + while (dispatchTimestampsMs.size() > 0 + && dispatchTimestampsMs.peekFirst() < timestampMs) { + final long dispatchTimestampMs = dispatchTimestampsMs.peekFirst(); + final long elapsedDurationMs = timestampMs - dispatchTimestampMs; + // Only increment the counts if the broadcast was sent not too long ago, as + // decided by 'broadcastResponseWindowDurationMs' and is part of a new session. + // That is, it occurred 'broadcastsSessionWithResponseDurationMs' after the + // previously handled broadcast event which is represented by + // 'broadcastsSessionEndTimestampMs'. + if (elapsedDurationMs <= broadcastResponseWindowDurationMs + && dispatchTimestampMs >= broadcastsSessionEndTimestampMs) { + if (broadcastsSessionEndTimestampMs != 0 + && !recordAllBroadcastsSessionsWithinResponseWindow) { break; - default: - Slog.wtf(TAG, "Unknown event: " + event); + } + final BroadcastResponseStats responseStats = + getOrCreateBroadcastResponseStats(broadcastEvent); + responseStats.incrementBroadcastsDispatchedCount(1); + broadcastsSessionEndTimestampMs = dispatchTimestampMs + + broadcastsSessionWithResponseDurationMs; + switch (event) { + case NOTIFICATION_EVENT_TYPE_POSTED: + responseStats.incrementNotificationsPostedCount(1); + break; + case NOTIFICATION_EVENT_TYPE_UPDATED: + responseStats.incrementNotificationsUpdatedCount(1); + break; + case NOTIFICATION_EVENT_TYPE_CANCELLED: + responseStats.incrementNotificationsCancelledCount(1); + break; + default: + Slog.wtf(TAG, "Unknown event: " + event); + } } + dispatchTimestampsMs.removeFirst(); } - broadcastEvents.removeAt(i); + if (dispatchTimestampsMs.size() == 0) { + broadcastEvents.removeAt(i); + } + } + } + } + + @GuardedBy("mLock") + private void recordAndPruneOldBroadcastDispatchTimestamps(BroadcastEvent broadcastEvent) { + final LongArrayQueue timestampsMs = broadcastEvent.getTimestampsMs(); + final long broadcastResponseWindowDurationMs = + mAppStandby.getBroadcastResponseWindowDurationMs(); + final long broadcastsSessionDurationMs = + mAppStandby.getBroadcastSessionsDurationMs(); + final long nowElapsedMs = SystemClock.elapsedRealtime(); + long broadcastsSessionEndTimestampMs = 0; + while (timestampsMs.size() > 0 + && timestampsMs.peekFirst() < (nowElapsedMs - broadcastResponseWindowDurationMs)) { + final long eventTimestampMs = timestampsMs.peekFirst(); + if (eventTimestampMs >= broadcastsSessionEndTimestampMs) { + final BroadcastResponseStats responseStats = + getOrCreateBroadcastResponseStats(broadcastEvent); + responseStats.incrementBroadcastsDispatchedCount(1); + broadcastsSessionEndTimestampMs = eventTimestampMs + broadcastsSessionDurationMs; } + timestampsMs.removeFirst(); } } @@ -247,7 +292,7 @@ class BroadcastResponseStatsTracker { @GuardedBy("mLock") @Nullable - private LongSparseArray<BroadcastEvent> getBroadcastEventsLocked( + private ArraySet<BroadcastEvent> getBroadcastEventsLocked( @NonNull String packageName, UserHandle user) { final UserBroadcastEvents userBroadcastEvents = mUserBroadcastEvents.get( user.getIdentifier()); @@ -259,7 +304,7 @@ class BroadcastResponseStatsTracker { @GuardedBy("mLock") @NonNull - private LongSparseArray<BroadcastEvent> getOrCreateBroadcastEventsLocked( + private ArraySet<BroadcastEvent> getOrCreateBroadcastEventsLocked( @NonNull String packageName, UserHandle user) { UserBroadcastEvents userBroadcastEvents = mUserBroadcastEvents.get(user.getIdentifier()); if (userBroadcastEvents == null) { @@ -272,16 +317,6 @@ class BroadcastResponseStatsTracker { @GuardedBy("mLock") @Nullable private BroadcastResponseStats getBroadcastResponseStats( - @NonNull BroadcastEvent broadcastEvent) { - final int sourceUid = broadcastEvent.getSourceUid(); - final SparseArray<UserBroadcastResponseStats> responseStatsForUid = - mUserResponseStats.get(sourceUid); - return getBroadcastResponseStats(responseStatsForUid, broadcastEvent); - } - - @GuardedBy("mLock") - @Nullable - private BroadcastResponseStats getBroadcastResponseStats( @Nullable SparseArray<UserBroadcastResponseStats> responseStatsForUid, @NonNull BroadcastEvent broadcastEvent) { if (responseStatsForUid == null) { @@ -315,6 +350,20 @@ class BroadcastResponseStatsTracker { return userResponseStats.getOrCreateBroadcastResponseStats(broadcastEvent); } + private static BroadcastEvent getOrCreateBroadcastEvent( + ArraySet<BroadcastEvent> broadcastEvents, + int sourceUid, String targetPackage, int targetUserId, long idForResponseEvent) { + final BroadcastEvent broadcastEvent = new BroadcastEvent( + sourceUid, targetPackage, targetUserId, idForResponseEvent); + final int index = broadcastEvents.indexOf(broadcastEvent); + if (index >= 0) { + return broadcastEvents.valueAt(index); + } else { + broadcastEvents.add(broadcastEvent); + return broadcastEvent; + } + } + void dump(@NonNull IndentingPrintWriter ipw) { ipw.println("Broadcast response stats:"); ipw.increaseIndent(); diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 6f89bb25e2ca..078177b3a89f 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -2768,18 +2768,9 @@ public class UsageStatsService extends SystemService implements throw new IllegalArgumentException("id needs to be >=0"); } - final int result = getContext().checkCallingOrSelfPermission( - android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS); - // STOPSHIP (206518114): Temporarily check for PACKAGE_USAGE_STATS permission as well - // until the clients switch to using the new permission. - if (result != PackageManager.PERMISSION_GRANTED) { - if (!hasPermission(callingPackage)) { - throw new SecurityException( - "Caller does not have the permission needed to call this API; " - + "callingPackage=" + callingPackage - + ", callingUid=" + Binder.getCallingUid()); - } - } + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS, + "queryBroadcastResponseStats"); final int callingUid = Binder.getCallingUid(); userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, false /* allowAll */, false /* requireFull */, @@ -2801,18 +2792,9 @@ public class UsageStatsService extends SystemService implements } - final int result = getContext().checkCallingOrSelfPermission( - android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS); - // STOPSHIP (206518114): Temporarily check for PACKAGE_USAGE_STATS permission as well - // until the clients switch to using the new permission. - if (result != PackageManager.PERMISSION_GRANTED) { - if (!hasPermission(callingPackage)) { - throw new SecurityException( - "Caller does not have the permission needed to call this API; " - + "callingPackage=" + callingPackage - + ", callingUid=" + Binder.getCallingUid()); - } - } + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS, + "clearBroadcastResponseStats"); final int callingUid = Binder.getCallingUid(); userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, false /* allowAll */, false /* requireFull */, @@ -2825,18 +2807,9 @@ public class UsageStatsService extends SystemService implements public void clearBroadcastEvents(@NonNull String callingPackage, @UserIdInt int userId) { Objects.requireNonNull(callingPackage); - final int result = getContext().checkCallingOrSelfPermission( - android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS); - // STOPSHIP (206518114): Temporarily check for PACKAGE_USAGE_STATS permission as well - // until the clients switch to using the new permission. - if (result != PackageManager.PERMISSION_GRANTED) { - if (!hasPermission(callingPackage)) { - throw new SecurityException( - "Caller does not have the permission needed to call this API; " - + "callingPackage=" + callingPackage - + ", callingUid=" + Binder.getCallingUid()); - } - } + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS, + "clearBroadcastEvents"); final int callingUid = Binder.getCallingUid(); userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, false /* allowAll */, false /* requireFull */, diff --git a/services/usage/java/com/android/server/usage/UserBroadcastEvents.java b/services/usage/java/com/android/server/usage/UserBroadcastEvents.java index 0ec59c3e022f..fcef01058cbc 100644 --- a/services/usage/java/com/android/server/usage/UserBroadcastEvents.java +++ b/services/usage/java/com/android/server/usage/UserBroadcastEvents.java @@ -19,7 +19,8 @@ package com.android.server.usage; import android.annotation.NonNull; import android.annotation.Nullable; import android.util.ArrayMap; -import android.util.LongSparseArray; +import android.util.ArraySet; +import android.util.LongArrayQueue; import android.util.TimeUtils; import com.android.internal.util.IndentingPrintWriter; @@ -30,17 +31,17 @@ class UserBroadcastEvents { * Here targetPackage refers to the package receiving the broadcast and BroadcastEvent objects * corresponding to each broadcast it is receiving. */ - private ArrayMap<String, LongSparseArray<BroadcastEvent>> mBroadcastEvents = new ArrayMap(); + private ArrayMap<String, ArraySet<BroadcastEvent>> mBroadcastEvents = new ArrayMap(); - @Nullable LongSparseArray<BroadcastEvent> getBroadcastEvents(@NonNull String packageName) { + @Nullable ArraySet<BroadcastEvent> getBroadcastEvents(@NonNull String packageName) { return mBroadcastEvents.get(packageName); } - @NonNull LongSparseArray<BroadcastEvent> getOrCreateBroadcastEvents( + @NonNull ArraySet<BroadcastEvent> getOrCreateBroadcastEvents( @NonNull String packageName) { - LongSparseArray<BroadcastEvent> broadcastEvents = mBroadcastEvents.get(packageName); + ArraySet<BroadcastEvent> broadcastEvents = mBroadcastEvents.get(packageName); if (broadcastEvents == null) { - broadcastEvents = new LongSparseArray<>(); + broadcastEvents = new ArraySet<>(); mBroadcastEvents.put(packageName, broadcastEvents); } return broadcastEvents; @@ -56,7 +57,7 @@ class UserBroadcastEvents { void clear(int uid) { for (int i = mBroadcastEvents.size() - 1; i >= 0; --i) { - final LongSparseArray<BroadcastEvent> broadcastEvents = mBroadcastEvents.valueAt(i); + final ArraySet<BroadcastEvent> broadcastEvents = mBroadcastEvents.valueAt(i); for (int j = broadcastEvents.size() - 1; j >= 0; --j) { if (broadcastEvents.valueAt(j).getSourceUid() == uid) { broadcastEvents.removeAt(j); @@ -68,18 +69,26 @@ class UserBroadcastEvents { void dump(@NonNull IndentingPrintWriter ipw) { for (int i = 0; i < mBroadcastEvents.size(); ++i) { final String packageName = mBroadcastEvents.keyAt(i); - final LongSparseArray<BroadcastEvent> broadcastEvents = mBroadcastEvents.valueAt(i); + final ArraySet<BroadcastEvent> broadcastEvents = mBroadcastEvents.valueAt(i); ipw.println(packageName + ":"); ipw.increaseIndent(); if (broadcastEvents.size() == 0) { ipw.println("<empty>"); } else { for (int j = 0; j < broadcastEvents.size(); ++j) { - final long timestampMs = broadcastEvents.keyAt(j); final BroadcastEvent broadcastEvent = broadcastEvents.valueAt(j); - TimeUtils.formatDuration(timestampMs, ipw); - ipw.print(": "); ipw.println(broadcastEvent); + ipw.increaseIndent(); + final LongArrayQueue timestampsMs = broadcastEvent.getTimestampsMs(); + for (int timestampIdx = 0; timestampIdx < timestampsMs.size(); ++timestampIdx) { + if (timestampIdx > 0) { + ipw.print(','); + } + final long timestampMs = timestampsMs.get(timestampIdx); + TimeUtils.formatDuration(timestampMs, ipw); + } + ipw.println(); + ipw.decreaseIndent(); } } ipw.decreaseIndent(); |