summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java22
-rw-r--r--apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java81
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java4
-rw-r--r--core/java/android/app/usage/OWNERS1
-rw-r--r--core/java/android/os/PowerManager.java73
-rw-r--r--core/java/android/os/PowerManagerInternal.java3
-rw-r--r--core/java/android/os/UserManager.java6
-rw-r--r--core/java/com/android/server/SystemConfig.java16
-rw-r--r--core/proto/android/server/vibrator/vibratormanagerservice.proto3
-rw-r--r--data/etc/privapp-permissions-platform.xml24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java46
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepository.kt56
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java35
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt142
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java67
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java10
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java17
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java3
-rw-r--r--services/core/java/com/android/server/pm/DexOptHelper.java53
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java4
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java21
-rw-r--r--services/core/java/com/android/server/vibrator/Vibration.java30
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationSettings.java56
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java19
-rw-r--r--services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java61
-rw-r--r--services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java111
-rw-r--r--services/usage/OWNERS3
-rw-r--r--services/usage/java/com/android/server/usage/BroadcastEvent.java11
-rw-r--r--services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java145
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java45
-rw-r--r--services/usage/java/com/android/server/usage/UserBroadcastEvents.java31
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();