Merge "Add better debug logs in Latency Tracker." into main
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 810be8f..fe95a59 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -63,7 +63,7 @@
name: "remove_user_during_user_switch"
namespace: "backstage_power"
description: "Remove started user if user will be stopped due to user switch"
- bug: "321598070"
+ bug: "337077643"
}
flag {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index a5a08fb..fe80d1b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1981,7 +1981,12 @@
jobStatus.getNumAppliedFlexibleConstraints(),
jobStatus.getNumDroppedFlexibleConstraints(),
jobStatus.getFilteredTraceTag(),
- jobStatus.getFilteredDebugTags());
+ jobStatus.getFilteredDebugTags(),
+ jobStatus.getNumAbandonedFailures(),
+ /* 0 is reserved for UNKNOWN_POLICY */
+ jobStatus.getJob().getBackoffPolicy() + 1,
+ shouldUseAggressiveBackoff(jobStatus.getNumAbandonedFailures()));
+
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
@@ -2422,7 +2427,11 @@
cancelled.getNumAppliedFlexibleConstraints(),
cancelled.getNumDroppedFlexibleConstraints(),
cancelled.getFilteredTraceTag(),
- cancelled.getFilteredDebugTags());
+ cancelled.getFilteredDebugTags(),
+ cancelled.getNumAbandonedFailures(),
+ /* 0 is reserved for UNKNOWN_POLICY */
+ cancelled.getJob().getBackoffPolicy() + 1,
+ shouldUseAggressiveBackoff(cancelled.getNumAbandonedFailures()));
}
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 909a9b3..2b401c8 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -546,7 +546,11 @@
job.getNumAppliedFlexibleConstraints(),
job.getNumDroppedFlexibleConstraints(),
job.getFilteredTraceTag(),
- job.getFilteredDebugTags());
+ job.getFilteredDebugTags(),
+ job.getNumAbandonedFailures(),
+ /* 0 is reserved for UNKNOWN_POLICY */
+ job.getJob().getBackoffPolicy() + 1,
+ mService.shouldUseAggressiveBackoff(job.getNumAbandonedFailures()));
sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount());
final String sourcePackage = job.getSourcePackageName();
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
@@ -1681,7 +1685,11 @@
completedJob.getNumAppliedFlexibleConstraints(),
completedJob.getNumDroppedFlexibleConstraints(),
completedJob.getFilteredTraceTag(),
- completedJob.getFilteredDebugTags());
+ completedJob.getFilteredDebugTags(),
+ completedJob.getNumAbandonedFailures(),
+ /* 0 is reserved for UNKNOWN_POLICY */
+ completedJob.getJob().getBackoffPolicy() + 1,
+ mService.shouldUseAggressiveBackoff(completedJob.getNumAbandonedFailures()));
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER,
JobSchedulerService.TRACE_TRACK_NAME, getId());
diff --git a/core/api/current.txt b/core/api/current.txt
index 0fe8717..612d0b7 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -2201,6 +2201,11 @@
field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardFastSpatialDamping;
field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardSlowEffectDamping;
field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardSlowSpatialDamping;
+ field @FlaggedApi("android.os.material_shape_tokens") public static final int config_shapeCornerRadiusLarge;
+ field @FlaggedApi("android.os.material_shape_tokens") public static final int config_shapeCornerRadiusMedium;
+ field @FlaggedApi("android.os.material_shape_tokens") public static final int config_shapeCornerRadiusSmall;
+ field @FlaggedApi("android.os.material_shape_tokens") public static final int config_shapeCornerRadiusXlarge;
+ field @FlaggedApi("android.os.material_shape_tokens") public static final int config_shapeCornerRadiusXsmall;
field public static final int dialog_min_width_major = 17104899; // 0x1050003
field public static final int dialog_min_width_minor = 17104900; // 0x1050004
field public static final int notification_large_icon_height = 17104902; // 0x1050006
@@ -24921,7 +24926,7 @@
method @Nullable public android.net.Uri getIconUri();
method @NonNull public String getId();
method @NonNull public CharSequence getName();
- method @FlaggedApi("com.android.media.flags.enable_route_visibility_control_api") @NonNull public java.util.Set<java.lang.String> getRequiredPermissions();
+ method @FlaggedApi("com.android.media.flags.enable_route_visibility_control_api") @NonNull public java.util.List<java.util.Set<java.lang.String>> getRequiredPermissions();
method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public int getSuitabilityStatus();
method @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") public int getSupportedRoutingTypes();
method public int getType();
@@ -24991,6 +24996,7 @@
method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle);
method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri);
method @FlaggedApi("com.android.media.flags.enable_route_visibility_control_api") @NonNull public android.media.MediaRoute2Info.Builder setRequiredPermissions(@NonNull java.util.Set<java.lang.String>);
+ method @FlaggedApi("com.android.media.flags.enable_route_visibility_control_api") @NonNull public android.media.MediaRoute2Info.Builder setRequiredPermissions(@NonNull java.util.List<java.util.Set<java.lang.String>>);
method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") @NonNull public android.media.MediaRoute2Info.Builder setSuitabilityStatus(int);
method @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") @NonNull public android.media.MediaRoute2Info.Builder setSupportedRoutingTypes(int);
method @NonNull public android.media.MediaRoute2Info.Builder setType(int);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a593970..edb30bd 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -550,6 +550,7 @@
field public static final int config_systemTextIntelligence = 17039414; // 0x1040036
field public static final int config_systemUi = 17039418; // 0x104003a
field public static final int config_systemUiIntelligence = 17039410; // 0x1040032
+ field @FlaggedApi("android.permission.flags.system_vendor_intelligence_role_enabled") public static final int config_systemVendorIntelligence;
field public static final int config_systemVisualIntelligence = 17039415; // 0x1040037
field public static final int config_systemWearHealthService = 17039428; // 0x1040044
field public static final int config_systemWellbeing = 17039408; // 0x1040030
@@ -5265,7 +5266,7 @@
@FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSession implements java.lang.AutoCloseable {
method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void close();
- method @Nullable public android.hardware.contexthub.HubServiceInfo getServiceInfo();
+ method @Nullable public String getServiceDescriptor();
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> sendMessage(@NonNull android.hardware.contexthub.HubMessage);
}
@@ -5318,7 +5319,7 @@
@FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointLifecycleCallback {
method public void onSessionClosed(@NonNull android.hardware.contexthub.HubEndpointSession, int);
- method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo, @Nullable android.hardware.contexthub.HubServiceInfo);
+ method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo, @Nullable String);
method public void onSessionOpened(@NonNull android.hardware.contexthub.HubEndpointSession);
}
@@ -6324,7 +6325,7 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int loadNanoApp(int, @NonNull android.hardware.location.NanoApp);
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> loadNanoApp(@NonNull android.hardware.location.ContextHubInfo, @NonNull android.hardware.location.NanoAppBinary);
method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void openSession(@NonNull android.hardware.contexthub.HubEndpoint, @NonNull android.hardware.contexthub.HubEndpointInfo);
- method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void openSession(@NonNull android.hardware.contexthub.HubEndpoint, @NonNull android.hardware.contexthub.HubEndpointInfo, @NonNull android.hardware.contexthub.HubServiceInfo);
+ method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void openSession(@NonNull android.hardware.contexthub.HubEndpoint, @NonNull android.hardware.contexthub.HubEndpointInfo, @NonNull String);
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.util.List<android.hardware.location.NanoAppState>> queryNanoApps(@NonNull android.hardware.location.ContextHubInfo);
method @Deprecated public int registerCallback(@NonNull android.hardware.location.ContextHubManager.Callback);
method @Deprecated public int registerCallback(android.hardware.location.ContextHubManager.Callback, android.os.Handler);
@@ -12615,6 +12616,7 @@
field public static final String ACTION_REQUEST_ENABLE_CONTENT_CAPTURE = "android.settings.REQUEST_ENABLE_CONTENT_CAPTURE";
field public static final String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS";
field public static final String ACTION_SHOW_RESTRICTED_SETTING_DIALOG = "android.settings.SHOW_RESTRICTED_SETTING_DIALOG";
+ field @FlaggedApi("com.android.internal.telephony.flags.action_sim_preference_settings") public static final String ACTION_SIM_PREFERENCE_SETTINGS = "android.settings.SIM_PREFERENCE_SETTINGS";
field public static final String ACTION_TETHER_PROVISIONING_UI = "android.settings.TETHER_PROVISIONING_UI";
field public static final String ACTION_TETHER_SETTINGS = "android.settings.TETHER_SETTINGS";
field public static final String ACTION_TETHER_UNSUPPORTED_CARRIER_UI = "android.settings.TETHER_UNSUPPORTED_CARRIER_UI";
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 69d3e8d..33ba058 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1189,18 +1189,6 @@
return procState == PROCESS_STATE_FOREGROUND_SERVICE;
}
- /** @hide Should this process state be considered jank perceptible? */
- public static final boolean isProcStateJankPerceptible(int procState) {
- if (Flags.jankPerceptibleNarrow()) {
- return procState == PROCESS_STATE_PERSISTENT_UI
- || procState == PROCESS_STATE_TOP
- || procState == PROCESS_STATE_IMPORTANT_FOREGROUND
- || procState == PROCESS_STATE_TOP_SLEEPING;
- } else {
- return !isProcStateCached(procState);
- }
- }
-
/** @hide requestType for assist context: only basic information. */
public static final int ASSIST_CONTEXT_BASIC = 0;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 3cc5ff0..27661ce 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3918,7 +3918,12 @@
if (mLastProcessState == processState) {
return;
}
- updateVmProcessState(mLastProcessState, processState);
+ // Do not issue a transitional GC if we are transitioning between 2 cached states.
+ // Only update if the state flips between cached and uncached or vice versa
+ if (ActivityManager.isProcStateCached(mLastProcessState)
+ != ActivityManager.isProcStateCached(processState)) {
+ updateVmProcessState(processState);
+ }
mLastProcessState = processState;
if (localLOGV) {
Slog.i(TAG, "******************* PROCESS STATE CHANGED TO: " + processState
@@ -3927,21 +3932,18 @@
}
}
- /** Converts a process state to a VM process state. */
- private static int toVmProcessState(int processState) {
- final int state = ActivityManager.isProcStateJankPerceptible(processState)
- ? VM_PROCESS_STATE_JANK_PERCEPTIBLE
- : VM_PROCESS_STATE_JANK_IMPERCEPTIBLE;
- return state;
- }
-
/** Update VM state based on ActivityManager.PROCESS_STATE_* constants. */
- private void updateVmProcessState(int lastProcessState, int newProcessState) {
- final int state = toVmProcessState(newProcessState);
- if (lastProcessState == PROCESS_STATE_UNKNOWN
- || state != toVmProcessState(lastProcessState)) {
- VMRuntime.getRuntime().updateProcessState(state);
- }
+ // Currently ART VM only uses state updates for Transitional GC, and thus
+ // this function initiates a Transitional GC for transitions into Cached apps states.
+ private void updateVmProcessState(int processState) {
+ // Only a transition into Cached state should result in a Transitional GC request
+ // to the ART runtime. Update VM state to JANK_IMPERCEPTIBLE in that case.
+ // Note that there are 4 possible cached states currently, all of which are
+ // JANK_IMPERCEPTIBLE from GC point of view.
+ final int state = ActivityManager.isProcStateCached(processState)
+ ? VM_PROCESS_STATE_JANK_IMPERCEPTIBLE
+ : VM_PROCESS_STATE_JANK_PERCEPTIBLE;
+ VMRuntime.getRuntime().updateProcessState(state);
}
@Override
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 1e971a5..9bb0ba4 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -1294,13 +1294,6 @@
public static record Args(@NonNull String mModule, @Nullable String mApi,
int mMaxEntries, boolean mIsolateUids, boolean mTestMode, boolean mCacheNulls) {
- /**
- * Default values for fields.
- */
- public static final int DEFAULT_MAX_ENTRIES = 32;
- public static final boolean DEFAULT_ISOLATE_UIDS = true;
- public static final boolean DEFAULT_CACHE_NULLS = false;
-
// Validation: the module must be one of the known module strings and the maxEntries must
// be positive.
public Args {
@@ -1315,10 +1308,10 @@
public Args(@NonNull String module) {
this(module,
null, // api
- DEFAULT_MAX_ENTRIES,
- DEFAULT_ISOLATE_UIDS,
+ 32, // maxEntries
+ true, // isolateUids
false, // testMode
- DEFAULT_CACHE_NULLS
+ true // allowNulls
);
}
@@ -1368,7 +1361,7 @@
* Burst a property name into module and api. Throw if the key is invalid. This method is
* used in to transition legacy cache constructors to the args constructor.
*/
- private static Args argsFromProperty(@NonNull String name) {
+ private static Args parseProperty(@NonNull String name) {
throwIfInvalidCacheKey(name);
// Strip off the leading well-known prefix.
String base = name.substring(CACHE_KEY_PREFIX.length() + 1);
@@ -1391,9 +1384,8 @@
*
* @hide
*/
- @Deprecated
public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) {
- this(argsFromProperty(propertyName).maxEntries(maxEntries), propertyName, null);
+ this(parseProperty(propertyName).maxEntries(maxEntries), propertyName, null);
}
/**
@@ -1407,10 +1399,9 @@
* @param cacheName Name of this cache in debug and dumpsys
* @hide
*/
- @Deprecated
public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName,
@NonNull String cacheName) {
- this(argsFromProperty(propertyName).maxEntries(maxEntries), cacheName, null);
+ this(parseProperty(propertyName).maxEntries(maxEntries), cacheName, null);
}
/**
@@ -1866,14 +1857,6 @@
}
/**
- * Invalidate caches in all processes that have the module and api specified in the args.
- * @hide
- */
- public static void invalidateCache(@NonNull Args args) {
- invalidateCache(createPropertyName(args.mModule, args.mApi));
- }
-
- /**
* Invalidate PropertyInvalidatedCache caches in all processes that are keyed on
* {@var name}. This function is synchronous: caches are invalidated upon return.
*
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 01cc9d8..76705dc 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -308,12 +308,18 @@
public boolean isSleeping;
/**
- * Whether the top activity fillsParent() is false
+ * Whether the top activity fillsParent() is false.
* @hide
*/
public boolean isTopActivityTransparent;
/**
+ * Whether fillsParent() is false for every activity in the tasks stack.
+ * @hide
+ */
+ public boolean isActivityStackTransparent;
+
+ /**
* The last non-fullscreen bounds the task was launched in or resized to.
* @hide
*/
@@ -489,6 +495,7 @@
&& parentTaskId == that.parentTaskId
&& Objects.equals(topActivity, that.topActivity)
&& isTopActivityTransparent == that.isTopActivityTransparent
+ && isActivityStackTransparent == that.isActivityStackTransparent
&& Objects.equals(lastNonFullscreenBounds, that.lastNonFullscreenBounds)
&& Objects.equals(capturedLink, that.capturedLink)
&& capturedLinkTimestamp == that.capturedLinkTimestamp
@@ -567,6 +574,7 @@
mTopActivityLocusId = source.readTypedObject(LocusId.CREATOR);
displayAreaFeatureId = source.readInt();
isTopActivityTransparent = source.readBoolean();
+ isActivityStackTransparent = source.readBoolean();
lastNonFullscreenBounds = source.readTypedObject(Rect.CREATOR);
capturedLink = source.readTypedObject(Uri.CREATOR);
capturedLinkTimestamp = source.readLong();
@@ -623,6 +631,7 @@
dest.writeTypedObject(mTopActivityLocusId, flags);
dest.writeInt(displayAreaFeatureId);
dest.writeBoolean(isTopActivityTransparent);
+ dest.writeBoolean(isActivityStackTransparent);
dest.writeTypedObject(lastNonFullscreenBounds, flags);
dest.writeTypedObject(capturedLink, flags);
dest.writeLong(capturedLinkTimestamp);
@@ -668,6 +677,7 @@
+ " locusId=" + mTopActivityLocusId
+ " displayAreaFeatureId=" + displayAreaFeatureId
+ " isTopActivityTransparent=" + isTopActivityTransparent
+ + " isActivityStackTransparent=" + isActivityStackTransparent
+ " lastNonFullscreenBounds=" + lastNonFullscreenBounds
+ " capturedLink=" + capturedLink
+ " capturedLinkTimestamp=" + capturedLinkTimestamp
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index 21ec585..44940ae 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -166,10 +166,3 @@
bug: "362537357"
is_exported: true
}
-
-flag {
- name: "jank_perceptible_narrow"
- namespace: "system_performance"
- description: "Narrow the scope of Jank Perceptible"
- bug: "304837972"
-}
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 361ba73..af035cb 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -343,16 +343,6 @@
}
flag {
- name: "dont_write_policy_definition"
- namespace: "enterprise"
- description: "Don't write redundant policy-definition-entry tags"
- bug: "335663055"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "active_admin_cleanup"
namespace: "enterprise"
description: "Remove ActiveAdmin from EnforcingAdmin and related cleanups"
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 8b6840c..7543fa9 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -284,6 +284,13 @@
}
flag {
+ name: "nm_binder_perf_cache_channels"
+ namespace: "systemui"
+ description: "Use IpcDataCache for notification channel/group lookups"
+ bug: "362981561"
+}
+
+flag {
name: "no_sbnholder"
namespace: "systemui"
description: "removes sbnholder from NLS"
diff --git a/core/java/android/hardware/contexthub/HubEndpoint.java b/core/java/android/hardware/contexthub/HubEndpoint.java
index 7efdd6d..1f12bbf 100644
--- a/core/java/android/hardware/contexthub/HubEndpoint.java
+++ b/core/java/android/hardware/contexthub/HubEndpoint.java
@@ -107,7 +107,7 @@
public void onSessionOpenRequest(
int sessionId,
HubEndpointInfo initiator,
- @Nullable HubServiceInfo serviceInfo)
+ @Nullable String serviceDescriptor)
throws RemoteException {
HubEndpointSession activeSession;
synchronized (mLock) {
@@ -128,16 +128,16 @@
processSessionOpenRequestResult(
sessionId,
initiator,
- serviceInfo,
+ serviceDescriptor,
mLifecycleCallback.onSessionOpenRequest(
- initiator, serviceInfo)));
+ initiator, serviceDescriptor)));
}
}
private void processSessionOpenRequestResult(
int sessionId,
HubEndpointInfo initiator,
- @Nullable HubServiceInfo serviceInfo,
+ @Nullable String serviceDescriptor,
HubEndpointSessionResult result) {
if (result == null) {
throw new IllegalArgumentException(
@@ -145,7 +145,7 @@
}
if (result.isAccepted()) {
- acceptSession(sessionId, initiator, serviceInfo);
+ acceptSession(sessionId, initiator, serviceDescriptor);
} else {
Log.i(
TAG,
@@ -162,7 +162,7 @@
private void acceptSession(
int sessionId,
HubEndpointInfo initiator,
- @Nullable HubServiceInfo serviceInfo) {
+ @Nullable String serviceDescriptor) {
if (mServiceToken == null || mAssignedHubEndpointInfo == null) {
// No longer registered?
return;
@@ -187,7 +187,7 @@
HubEndpoint.this,
mAssignedHubEndpointInfo,
initiator,
- serviceInfo);
+ serviceDescriptor);
try {
// oneway call to notify system service that the request is completed
mServiceToken.openSessionRequestComplete(sessionId);
@@ -334,7 +334,6 @@
@Nullable IHubEndpointMessageCallback endpointMessageCallback,
@NonNull Executor messageCallbackExecutor) {
mPendingHubEndpointInfo = pendingEndpointInfo;
-
mLifecycleCallback = endpointLifecycleCallback;
mLifecycleCallbackExecutor = lifecycleCallbackExecutor;
mMessageCallback = endpointMessageCallback;
@@ -387,7 +386,7 @@
}
/** @hide */
- public void openSession(HubEndpointInfo destinationInfo, @Nullable HubServiceInfo serviceInfo) {
+ public void openSession(HubEndpointInfo destinationInfo, @Nullable String serviceDescriptor) {
// TODO(b/378974199): Consider refactor these assertions
if (mServiceToken == null || mAssignedHubEndpointInfo == null) {
// No longer registered?
@@ -397,7 +396,7 @@
HubEndpointSession newSession;
try {
// Request system service to assign session id.
- int sessionId = mServiceToken.openSession(destinationInfo, serviceInfo);
+ int sessionId = mServiceToken.openSession(destinationInfo, serviceDescriptor);
// Save the newly created session
synchronized (mLock) {
@@ -407,7 +406,7 @@
HubEndpoint.this,
destinationInfo,
mAssignedHubEndpointInfo,
- serviceInfo);
+ serviceDescriptor);
mActiveSessions.put(sessionId, newSession);
}
} catch (RemoteException e) {
diff --git a/core/java/android/hardware/contexthub/HubEndpointSession.java b/core/java/android/hardware/contexthub/HubEndpointSession.java
index b3d65c1..b8af398 100644
--- a/core/java/android/hardware/contexthub/HubEndpointSession.java
+++ b/core/java/android/hardware/contexthub/HubEndpointSession.java
@@ -44,7 +44,7 @@
@NonNull private final HubEndpoint mHubEndpoint;
@NonNull private final HubEndpointInfo mInitiator;
@NonNull private final HubEndpointInfo mDestination;
- @Nullable private final HubServiceInfo mServiceInfo;
+ @Nullable private final String mServiceDescriptor;
private final AtomicBoolean mIsClosed = new AtomicBoolean(true);
@@ -54,12 +54,12 @@
@NonNull HubEndpoint hubEndpoint,
@NonNull HubEndpointInfo destination,
@NonNull HubEndpointInfo initiator,
- @Nullable HubServiceInfo serviceInfo) {
+ @Nullable String serviceDescriptor) {
mId = id;
mHubEndpoint = hubEndpoint;
mDestination = destination;
mInitiator = initiator;
- mServiceInfo = serviceInfo;
+ mServiceDescriptor = serviceDescriptor;
}
/**
@@ -131,8 +131,8 @@
}
/**
- * Get the {@link HubServiceInfo} associated with this session. Null value indicates that there
- * is no service associated to this session.
+ * Get the service descriptor associated with this session. Null value indicates that there is
+ * no service associated to this session.
*
* <p>For hub initiated sessions, the object was previously used in as an argument for open
* request in {@link IHubEndpointLifecycleCallback#onSessionOpenRequest}.
@@ -141,8 +141,8 @@
* android.hardware.location.ContextHubManager#openSession}
*/
@Nullable
- public HubServiceInfo getServiceInfo() {
- return mServiceInfo;
+ public String getServiceDescriptor() {
+ return mServiceDescriptor;
}
@Override
diff --git a/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl b/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
index 1c98b4b..b76b227 100644
--- a/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
+++ b/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
@@ -34,11 +34,12 @@
* Request system service to open a session with a specific destination.
*
* @param destination A valid HubEndpointInfo representing the destination.
+ * @param serviceDescriptor An optional descriptor of the service to scope this session to.
*
* @throws IllegalArgumentException If the HubEndpointInfo is not valid.
* @throws IllegalStateException If there are too many opened sessions.
*/
- int openSession(in HubEndpointInfo destination, in @nullable HubServiceInfo serviceInfo);
+ int openSession(in HubEndpointInfo destination, in @nullable String serviceDescriptor);
/**
* Request system service to close a specific session
diff --git a/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl b/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl
index 1ae5fb9..63edda8 100644
--- a/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl
+++ b/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl
@@ -29,9 +29,9 @@
*
* @param sessionId An integer identifying the session, assigned by the initiator
* @param initiator HubEndpointInfo representing the requester
- * @param serviceInfo Nullable HubServiceInfo representing the service associated with this session
+ * @param serviceDescriptor Nullable string representing the service associated with this session
*/
- void onSessionOpenRequest(int sessionId, in HubEndpointInfo initiator, in @nullable HubServiceInfo serviceInfo);
+ void onSessionOpenRequest(int sessionId, in HubEndpointInfo initiator, in @nullable String serviceDescriptor);
/**
* Request from system service to close a specific session
diff --git a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java b/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
index fe449bb..698ed0a 100644
--- a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
+++ b/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
@@ -34,12 +34,12 @@
* Called when an endpoint is requesting a session be opened with another endpoint.
*
* @param requester The {@link HubEndpointInfo} object representing the requester
- * @param serviceInfo The {@link HubServiceInfo} object representing the service associated with
- * this session. Null indicates that there is no service associated with this session.
+ * @param serviceDescriptor A string describing the service associated with this session. Null
+ * indicates that there is no service associated with this session.
*/
@NonNull
HubEndpointSessionResult onSessionOpenRequest(
- @NonNull HubEndpointInfo requester, @Nullable HubServiceInfo serviceInfo);
+ @NonNull HubEndpointInfo requester, @Nullable String serviceDescriptor);
/**
* Called when a communication session is opened and ready to be used.
diff --git a/core/java/android/hardware/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java
index 54d0dd0..1f7d426 100644
--- a/core/java/android/hardware/display/DisplayTopology.java
+++ b/core/java/android/hardware/display/DisplayTopology.java
@@ -582,6 +582,15 @@
}
}
+ /** Returns the graph representation of the topology */
+ public DisplayTopologyGraph getGraph() {
+ // TODO(b/364907904): implement
+ return new DisplayTopologyGraph(mPrimaryDisplayId,
+ new DisplayTopologyGraph.DisplayNode[] { new DisplayTopologyGraph.DisplayNode(
+ mRoot == null ? Display.DEFAULT_DISPLAY : mRoot.mDisplayId,
+ new DisplayTopologyGraph.AdjacentDisplay[0])});
+ }
+
/**
* Tests whether two brightness float values are within a small enough tolerance
* of each other.
diff --git a/core/java/android/hardware/display/DisplayTopologyGraph.java b/core/java/android/hardware/display/DisplayTopologyGraph.java
new file mode 100644
index 0000000..938e6d1
--- /dev/null
+++ b/core/java/android/hardware/display/DisplayTopologyGraph.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.display;
+
+/**
+ * Graph of the displays in {@link android.hardware.display.DisplayTopology} tree.
+ *
+ * @hide
+ */
+public record DisplayTopologyGraph(int primaryDisplayId, DisplayNode[] displayNodes) {
+ /**
+ * Display in the topology
+ */
+ public record DisplayNode(
+ int displayId,
+ AdjacentDisplay[] adjacentDisplays) {}
+
+ /**
+ * Edge to adjacent display
+ */
+ public record AdjacentDisplay(
+ // The logical Id of this adjacent display
+ int displayId,
+ // Side of the other display which touches this adjacent display.
+ @DisplayTopology.TreeNode.Position
+ int position,
+ // How many px this display is shifted along the touchingSide, can be negative.
+ float offsetPx) {}
+}
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 3f9317a..f8f7f5e 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -28,7 +28,6 @@
import static com.android.hardware.input.Flags.mouseReverseVerticalScrolling;
import static com.android.hardware.input.Flags.mouseSwapPrimaryButton;
import static com.android.hardware.input.Flags.touchpadSystemGestureDisable;
-import static com.android.hardware.input.Flags.touchpadTapDragging;
import static com.android.hardware.input.Flags.touchpadThreeFingerTapShortcut;
import static com.android.hardware.input.Flags.touchpadVisualizer;
import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
@@ -366,15 +365,6 @@
}
/**
- * Returns true if the feature flag for touchpad tap dragging is enabled.
- *
- * @hide
- */
- public static boolean isTouchpadTapDraggingFeatureFlagEnabled() {
- return touchpadTapDragging();
- }
-
- /**
* Returns true if the feature flag for disabling system gestures on touchpads is enabled.
*
* @hide
@@ -461,9 +451,6 @@
* @hide
*/
public static boolean useTouchpadTapDragging(@NonNull Context context) {
- if (!isTouchpadTapDraggingFeatureFlagEnabled()) {
- return false;
- }
return Settings.System.getIntForUser(context.getContentResolver(),
Settings.System.TOUCHPAD_TAP_DRAGGING, 0, UserHandle.USER_CURRENT) == 1;
}
@@ -480,9 +467,6 @@
*/
@RequiresPermission(Manifest.permission.WRITE_SETTINGS)
public static void setTouchpadTapDragging(@NonNull Context context, boolean enabled) {
- if (!isTouchpadTapDraggingFeatureFlagEnabled()) {
- return;
- }
Settings.System.putIntForUser(context.getContentResolver(),
Settings.System.TOUCHPAD_TAP_DRAGGING, enabled ? 1 : 0,
UserHandle.USER_CURRENT);
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index ebb6172..eb7b409 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -37,13 +37,6 @@
flag {
namespace: "input_native"
- name: "touchpad_tap_dragging"
- description: "Offers a setting to enable touchpad tap dragging"
- bug: "321978150"
-}
-
-flag {
- namespace: "input_native"
name: "keyboard_glyph_map"
description: "Allows system to provide keyboard specific key drawables and shortcuts via config files"
bug: "345440920"
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 3a74130..310e1a6 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -1309,16 +1309,16 @@
* ContextHubManager#registerEndpoint(HubEndpoint)}.
* @param destination {@link HubEndpointInfo} object that represents an endpoint from previous
* endpoint discovery results (e.g. from {@link ContextHubManager#findEndpoints(long)}).
- * @param serviceInfo {@link HubServiceInfo} object that describes the service associated with
- * this session. The information will be sent to the destination as part of open request.
+ * @param serviceDescriptor A string that describes the service associated with this session.
+ * The information will be sent to the destination as part of open request.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
@FlaggedApi(Flags.FLAG_OFFLOAD_API)
public void openSession(
@NonNull HubEndpoint hubEndpoint,
@NonNull HubEndpointInfo destination,
- @NonNull HubServiceInfo serviceInfo) {
- hubEndpoint.openSession(destination, serviceInfo);
+ @NonNull String serviceDescriptor) {
+ hubEndpoint.openSession(destination, serviceDescriptor);
}
/**
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 8b6da7e..84ca5ed 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -403,7 +403,7 @@
* increase when the hardware manufacturer provides an OTA update.
* <p>
* This constant records the major version of Android. Use {@link
- * SDK_INT_FULL} if you need to consider the minor version of Android
+ * #SDK_INT_FULL} if you need to consider the minor version of Android
* as well.
* <p>
* Possible values are defined in {@link Build.VERSION_CODES}.
diff --git a/core/java/android/os/IpcDataCache.java b/core/java/android/os/IpcDataCache.java
index a2e9314..8db1567 100644
--- a/core/java/android/os/IpcDataCache.java
+++ b/core/java/android/os/IpcDataCache.java
@@ -400,11 +400,10 @@
}
/**
- * This is a convenience class that encapsulates configuration information for a cache. It
- * may be supplied to the cache constructors in lieu of the other parameters. The class
- * captures maximum entry count, the module, the key, and the api. The key is used to
- * invalidate the cache and may be shared by different caches. The api is a user-visible (in
- * debug) name for the cache.
+ * This is a convenience class that encapsulates configuration information for a
+ * cache. It may be supplied to the cache constructors in lieu of the other
+ * parameters. The class captures maximum entry count, the module, the key, and the
+ * api.
*
* There are three specific use cases supported by this class.
*
@@ -431,8 +430,11 @@
* @hide
*/
public static class Config {
- final Args mArgs;
- final String mName;
+ private final int mMaxEntries;
+ @IpcDataCacheModule
+ private final String mModule;
+ private final String mApi;
+ private final String mName;
/**
* The list of cache names that were created extending this Config. If
@@ -450,20 +452,12 @@
*/
private boolean mDisabled = false;
- /**
- * Fully construct a config.
- */
- private Config(@NonNull Args args, @NonNull String name) {
- mArgs = args;
- mName = name;
- }
-
- /**
- *
- */
public Config(int maxEntries, @NonNull @IpcDataCacheModule String module,
@NonNull String api, @NonNull String name) {
- this(new Args(module).api(api).maxEntries(maxEntries), name);
+ mMaxEntries = maxEntries;
+ mModule = module;
+ mApi = api;
+ mName = name;
}
/**
@@ -479,7 +473,7 @@
* the parameter list.
*/
public Config(@NonNull Config root, @NonNull String api, @NonNull String name) {
- this(root.mArgs.api(api), name);
+ this(root.maxEntries(), root.module(), api, name);
}
/**
@@ -487,7 +481,7 @@
* the parameter list.
*/
public Config(@NonNull Config root, @NonNull String api) {
- this(root.mArgs.api(api), api);
+ this(root.maxEntries(), root.module(), api, api);
}
/**
@@ -496,23 +490,26 @@
* current process.
*/
public Config child(@NonNull String name) {
- final Config result = new Config(mArgs, name);
+ final Config result = new Config(this, api(), name);
registerChild(name);
return result;
}
- /**
- * Set the cacheNull behavior.
- */
- public Config cacheNulls(boolean enable) {
- return new Config(mArgs.cacheNulls(enable), mName);
+ public final int maxEntries() {
+ return mMaxEntries;
}
- /**
- * Set the isolateUidss behavior.
- */
- public Config isolateUids(boolean enable) {
- return new Config(mArgs.isolateUids(enable), mName);
+ @IpcDataCacheModule
+ public final @NonNull String module() {
+ return mModule;
+ }
+
+ public final @NonNull String api() {
+ return mApi;
+ }
+
+ public final @NonNull String name() {
+ return mName;
}
/**
@@ -535,7 +532,7 @@
* Invalidate all caches that share this Config's module and api.
*/
public void invalidateCache() {
- IpcDataCache.invalidateCache(mArgs);
+ IpcDataCache.invalidateCache(mModule, mApi);
}
/**
@@ -567,7 +564,8 @@
* @hide
*/
public IpcDataCache(@NonNull Config config, @NonNull QueryHandler<Query, Result> computer) {
- super(config.mArgs, config.mName, computer);
+ super(new Args(config.module()).maxEntries(config.maxEntries()).api(config.api()),
+ config.name(), computer);
}
/**
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index bfcc5cc..8d35338 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -118,9 +118,10 @@
# Memory
per-file OomKillRecord.java = file:/MEMORY_OWNERS
-# MessageQueue
+# MessageQueue and related classes
per-file MessageQueue.java = mfasheh@google.com, shayba@google.com
per-file Message.java = mfasheh@google.com, shayba@google.com
+per-file TestLooperManager.java = mfasheh@google.com, shayba@google.com
# Stats
per-file IStatsBootstrapAtomService.aidl = file:/services/core/java/com/android/server/stats/OWNERS
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 6357baa..2a46738 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -233,6 +233,14 @@
}
flag {
+ name: "material_shape_tokens"
+ namespace: "systemui"
+ description: "Adding new Material Tokens for M3 Shape (corner radius) Spec"
+ bug: "324928718"
+ is_exported: true
+}
+
+flag {
name: "message_queue_tail_tracking"
namespace: "system_performance"
description: "track tail of message queue."
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 57e1b58..2c4883f 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -483,3 +483,12 @@
description: "Rate limit async noteOp callbacks for batched noteOperation binder call"
bug: "366013082"
}
+
+flag {
+ name: "system_vendor_intelligence_role_enabled"
+ is_exported: true
+ is_fixed_read_only: true
+ namespace: "permissions"
+ description: "This flag is used to enable the role system_vendor_intelligence"
+ bug: "377553620"
+}
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index e4a3c9f..25e8a4d 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -213,7 +213,10 @@
private int mPreferenceHeaderItemResId = 0;
private boolean mPreferenceHeaderRemoveEmptyIcon = false;
+ private boolean mIsBackCallbackRegistered = false;
private final OnBackInvokedCallback mOnBackInvokedCallback = this::onBackInvoked;
+ private final FragmentManager.OnBackStackChangedListener mOnBackStackChangedListener =
+ this::updateBackCallbackRegistrationState;
/**
* The starting request code given out to preference framework.
@@ -706,6 +709,7 @@
}
}
updateBackCallbackRegistrationState();
+ getFragmentManager().addOnBackStackChangedListener(mOnBackStackChangedListener);
}
@Override
@@ -715,17 +719,25 @@
private void updateBackCallbackRegistrationState() {
if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(this)) return;
- if (mCurHeader != null && mSinglePane && getFragmentManager().getBackStackEntryCount() == 0
- && getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT) == null) {
- getOnBackInvokedDispatcher()
- .registerOnBackInvokedCallback(PRIORITY_DEFAULT, mOnBackInvokedCallback);
- } else {
+ if ((mCurHeader != null && getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT) == null
+ && mSinglePane) || getFragmentManager().getBackStackEntryCount() != 0) {
+ if (!mIsBackCallbackRegistered) {
+ getOnBackInvokedDispatcher()
+ .registerOnBackInvokedCallback(PRIORITY_DEFAULT, mOnBackInvokedCallback);
+ mIsBackCallbackRegistered = true;
+ }
+ } else if (mIsBackCallbackRegistered) {
getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mOnBackInvokedCallback);
+ mIsBackCallbackRegistered = false;
}
}
private void onBackInvoked() {
- if (mCurHeader != null && mSinglePane && getFragmentManager().getBackStackEntryCount() == 0
+ if (WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(this)
+ && getFragmentManager().getBackStackEntryCount() != 0) {
+ getFragmentManager().popBackStackImmediate();
+ } else if (mCurHeader != null && mSinglePane
+ && getFragmentManager().getBackStackEntryCount() == 0
&& getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT) == null) {
mCurHeader = null;
@@ -1012,6 +1024,7 @@
@Override
protected void onDestroy() {
+ getFragmentManager().removeOnBackStackChangedListener(mOnBackStackChangedListener);
mHandler.removeMessages(MSG_BIND_PREFERENCES);
mHandler.removeMessages(MSG_BUILD_HEADERS);
super.onDestroy();
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 0ae9ffa..9935be2 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2469,6 +2469,25 @@
= "android.settings.APP_NOTIFICATION_BUBBLE_SETTINGS";
/**
+ * Activity Action: Show the settings for users to select their preferred SIM subscription
+ * when a new SIM subscription has become available.
+ * <p>
+ * This Activity will only launch successfully if the newly active subscription ID is set as the
+ * value of {@link EXTRA_SUB_ID} and the value corresponds with an active SIM subscription.
+ * <p>
+ * Input: {@link #EXTRA_SUB_ID}: the subscription ID of the newly active SIM subscription.
+ * <p>
+ * Output: Nothing.
+ *
+ * @hide
+ */
+ @FlaggedApi(com.android.internal.telephony.flags.Flags.FLAG_ACTION_SIM_PREFERENCE_SETTINGS)
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SIM_PREFERENCE_SETTINGS =
+ "android.settings.SIM_PREFERENCE_SETTINGS";
+
+ /**
* Intent Extra: The value of {@link android.app.settings.SettingsEnums#EntryPointType} for
* settings metrics that logs the entry point about physical keyboard settings.
* <p>
diff --git a/core/java/android/security/net/config/CertificatesEntryRef.java b/core/java/android/security/net/config/CertificatesEntryRef.java
index 45cd0f0..a46049f 100644
--- a/core/java/android/security/net/config/CertificatesEntryRef.java
+++ b/core/java/android/security/net/config/CertificatesEntryRef.java
@@ -17,6 +17,7 @@
package android.security.net.config;
import android.util.ArraySet;
+
import java.security.cert.X509Certificate;
import java.util.Set;
@@ -24,16 +25,23 @@
public final class CertificatesEntryRef {
private final CertificateSource mSource;
private final boolean mOverridesPins;
+ private final boolean mDisableCT;
- public CertificatesEntryRef(CertificateSource source, boolean overridesPins) {
+ public CertificatesEntryRef(CertificateSource source, boolean overridesPins,
+ boolean disableCT) {
mSource = source;
mOverridesPins = overridesPins;
+ mDisableCT = disableCT;
}
boolean overridesPins() {
return mOverridesPins;
}
+ boolean disableCT() {
+ return mDisableCT;
+ }
+
public Set<TrustAnchor> getTrustAnchors() {
// TODO: cache this [but handle mutable sources]
Set<TrustAnchor> anchors = new ArraySet<TrustAnchor>();
diff --git a/core/java/android/security/net/config/KeyStoreConfigSource.java b/core/java/android/security/net/config/KeyStoreConfigSource.java
index 8d4f098..a54d8d0 100644
--- a/core/java/android/security/net/config/KeyStoreConfigSource.java
+++ b/core/java/android/security/net/config/KeyStoreConfigSource.java
@@ -17,8 +17,8 @@
package android.security.net.config;
import android.util.Pair;
+
import java.security.KeyStore;
-import java.security.KeyStoreException;
import java.util.Set;
/**
@@ -32,7 +32,7 @@
mConfig = new NetworkSecurityConfig.Builder()
.addCertificatesEntryRef(
// Use the KeyStore and do not override pins (of which there are none).
- new CertificatesEntryRef(new KeyStoreCertificateSource(ks), false))
+ new CertificatesEntryRef(new KeyStoreCertificateSource(ks), false, false))
.build();
}
diff --git a/core/java/android/security/net/config/NetworkSecurityConfig.java b/core/java/android/security/net/config/NetworkSecurityConfig.java
index 129ae63..410c68b 100644
--- a/core/java/android/security/net/config/NetworkSecurityConfig.java
+++ b/core/java/android/security/net/config/NetworkSecurityConfig.java
@@ -112,7 +112,6 @@
return mHstsEnforced;
}
- // TODO(b/28746284): add exceptions for user-added certificates and enterprise overrides.
public boolean isCertificateTransparencyVerificationRequired() {
return mCertificateTransparencyVerificationRequired;
}
@@ -192,20 +191,21 @@
* @hide
*/
public static Builder getDefaultBuilder(ApplicationInfo info) {
+ // System certificate store, does not bypass static pins, does not disable CT.
+ CertificatesEntryRef systemRef = new CertificatesEntryRef(
+ SystemCertificateSource.getInstance(), false, false);
Builder builder = new Builder()
.setHstsEnforced(DEFAULT_HSTS_ENFORCED)
- // System certificate store, does not bypass static pins.
- .addCertificatesEntryRef(
- new CertificatesEntryRef(SystemCertificateSource.getInstance(), false));
+ .addCertificatesEntryRef(systemRef);
final boolean cleartextTrafficPermitted = info.targetSdkVersion < Build.VERSION_CODES.P
&& !info.isInstantApp();
builder.setCleartextTrafficPermitted(cleartextTrafficPermitted);
// Applications targeting N and above must opt in into trusting the user added certificate
// store.
if (info.targetSdkVersion <= Build.VERSION_CODES.M && !info.isPrivilegedApp()) {
- // User certificate store, does not bypass static pins.
+ // User certificate store, does not bypass static pins. CT is disabled.
builder.addCertificatesEntryRef(
- new CertificatesEntryRef(UserCertificateSource.getInstance(), false));
+ new CertificatesEntryRef(UserCertificateSource.getInstance(), false, true));
}
return builder;
}
@@ -339,6 +339,16 @@
if (mCertificateTransparencyVerificationRequiredSet) {
return mCertificateTransparencyVerificationRequired;
}
+ // CT verification has not been set explicitly. Before deferring to
+ // the parent, check if any of the CertificatesEntryRef requires it
+ // to be disabled (i.e., user store or inline certificate).
+ if (hasCertificatesEntryRefs()) {
+ for (CertificatesEntryRef ref : getCertificatesEntryRefs()) {
+ if (ref.disableCT()) {
+ return false;
+ }
+ }
+ }
if (mParentBuilder != null) {
return mParentBuilder.getCertificateTransparencyVerificationRequired();
}
diff --git a/core/java/android/security/net/config/XmlConfigSource.java b/core/java/android/security/net/config/XmlConfigSource.java
index b1c1479..95e579f 100644
--- a/core/java/android/security/net/config/XmlConfigSource.java
+++ b/core/java/android/security/net/config/XmlConfigSource.java
@@ -182,6 +182,7 @@
boolean overridePins =
parser.getAttributeBooleanValue(null, "overridePins", defaultOverridePins);
int sourceId = parser.getAttributeResourceValue(null, "src", -1);
+ boolean disableCT = false;
String sourceString = parser.getAttributeValue(null, "src");
CertificateSource source = null;
if (sourceString == null) {
@@ -190,10 +191,12 @@
if (sourceId != -1) {
// TODO: Cache ResourceCertificateSources by sourceId
source = new ResourceCertificateSource(sourceId, mContext);
+ disableCT = true;
} else if ("system".equals(sourceString)) {
source = SystemCertificateSource.getInstance();
} else if ("user".equals(sourceString)) {
source = UserCertificateSource.getInstance();
+ disableCT = true;
} else if ("wfa".equals(sourceString)) {
source = WfaCertificateSource.getInstance();
} else {
@@ -201,7 +204,7 @@
+ "Should be one of system|user|@resourceVal");
}
XmlUtils.skipCurrentTag(parser);
- return new CertificatesEntryRef(source, overridePins);
+ return new CertificatesEntryRef(source, overridePins, disableCT);
}
private Collection<CertificatesEntryRef> parseTrustAnchors(XmlResourceParser parser,
diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
index 42dbd37..8add9f7 100644
--- a/core/java/android/security/responsible_apis_flags.aconfig
+++ b/core/java/android/security/responsible_apis_flags.aconfig
@@ -97,14 +97,6 @@
}
flag {
- name: "prevent_intent_redirect_show_toast_if_nested_keys_not_collected"
- namespace: "responsible_apis"
- description: "Prevent intent redirect attacks by showing a toast if not yet collected"
- bug: "361143368"
- is_fixed_read_only: true
-}
-
-flag {
name: "prevent_intent_redirect_show_toast_if_nested_keys_not_collected_r_w"
namespace: "responsible_apis"
description: "Prevent intent redirect attacks by showing a toast if not yet collected"
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index b796e0b..ba20839 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -357,6 +357,31 @@
} else if (mTmpFrame.right == relativeFrame.right) {
return Insets.of(0, 0, mTmpFrame.width(), 0);
}
+ } else {
+ // The source doesn't cover the width or the height of relativeFrame, but just parts of
+ // them. Here uses mSideHint to decide which side should be inset.
+ switch (mSideHint) {
+ case SIDE_LEFT:
+ if (mTmpFrame.left == relativeFrame.left) {
+ return Insets.of(mTmpFrame.width(), 0, 0, 0);
+ }
+ break;
+ case SIDE_TOP:
+ if (mTmpFrame.top == relativeFrame.top) {
+ return Insets.of(0, mTmpFrame.height(), 0, 0);
+ }
+ break;
+ case SIDE_RIGHT:
+ if (mTmpFrame.right == relativeFrame.right) {
+ return Insets.of(0, 0, mTmpFrame.width(), 0);
+ }
+ break;
+ case SIDE_BOTTOM:
+ if (mTmpFrame.bottom == relativeFrame.bottom) {
+ return Insets.of(0, 0, 0, mTmpFrame.height());
+ }
+ break;
+ }
}
return Insets.NONE;
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 049189f..0681745 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -28277,14 +28277,28 @@
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
- if (mParent != null && !mParent.isLayoutRequested()) {
- mParent.requestLayout();
+ if (mParent != null) {
+ if (!mParent.isLayoutRequested()) {
+ mParent.requestLayout();
+ } else {
+ clearMeasureCacheOfAncestors();
+ }
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
+ private void clearMeasureCacheOfAncestors() {
+ ViewParent parent = mParent;
+ while (parent instanceof View view) {
+ if (view.mMeasureCache != null) {
+ view.mMeasureCache.clear();
+ }
+ parent = view.mParent;
+ }
+ }
+
/**
* Forces this view to be laid out during the next layout pass.
* This method does not call requestLayout() or forceLayout()
@@ -28640,8 +28654,10 @@
*/
@RemotableViewMethod
public void setMinimumHeight(int minHeight) {
- mMinHeight = minHeight;
- requestLayout();
+ if (mMinHeight != minHeight) {
+ mMinHeight = minHeight;
+ requestLayout();
+ }
}
/**
@@ -28671,8 +28687,10 @@
*/
@RemotableViewMethod
public void setMinimumWidth(int minWidth) {
- mMinWidth = minWidth;
- requestLayout();
+ if (mMinWidth != minWidth) {
+ mMinWidth = minWidth;
+ requestLayout();
+ }
}
diff --git a/core/java/com/android/internal/policy/KeyInterceptionInfo.java b/core/java/com/android/internal/policy/KeyInterceptionInfo.java
index fed8fe3..b20f6d2 100644
--- a/core/java/com/android/internal/policy/KeyInterceptionInfo.java
+++ b/core/java/com/android/internal/policy/KeyInterceptionInfo.java
@@ -27,13 +27,11 @@
// Debug friendly name to help identify the window
public final String windowTitle;
public final int windowOwnerUid;
- public final int inputFeaturesFlags;
- public KeyInterceptionInfo(int type, int flags, String title, int uid, int inputFeaturesFlags) {
+ public KeyInterceptionInfo(int type, int flags, String title, int uid) {
layoutParamsType = type;
layoutParamsPrivateFlags = flags;
windowTitle = title;
windowOwnerUid = uid;
- this.inputFeaturesFlags = inputFeaturesFlags;
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
index da880e1..1e9ba78 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -697,6 +697,7 @@
}
}
}
+ op.markNotDirty();
op.apply(context);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
index 11e58ba..5c3df7e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
@@ -333,6 +333,7 @@
public void overrideColor(int id, int color) {
mColorOverride[id] = true;
mColorMap.put(id, color);
+ updateListeners(id);
}
/** Clear the color Overrides */
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
index 29124d0..bb112d1 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
@@ -132,9 +132,6 @@
@Override
public void apply(@NonNull RemoteContext context) {
- if (context.getMode() != RemoteContext.ContextMode.DATA) {
- return;
- }
context.addClickArea(
mId, mContentDescription, mOutLeft, mOutTop, mOutRight, mOutBottom, mMetadata);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
index 6c9105d..e9aae1e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
@@ -64,6 +64,7 @@
@Override
public void apply(@NonNull RemoteContext context) {
context.setTheme(mTheme);
+ markDirty();
}
@NonNull
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index b7a7f96..b39bacd 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -220,6 +220,7 @@
"android_hardware_camera2_utils_SurfaceUtils.cpp",
"android_hardware_display_DisplayManagerGlobal.cpp",
"android_hardware_display_DisplayViewport.cpp",
+ "android_hardware_display_DisplayTopology.cpp",
"android_hardware_HardwareBuffer.cpp",
"android_hardware_OverlayProperties.cpp",
"android_hardware_SensorManager.cpp",
diff --git a/core/jni/android_hardware_display_DisplayTopology.cpp b/core/jni/android_hardware_display_DisplayTopology.cpp
new file mode 100644
index 0000000..d9e802d
--- /dev/null
+++ b/core/jni/android_hardware_display_DisplayTopology.cpp
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "DisplayTopology-JNI"
+
+#include <android_hardware_display_DisplayTopology.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <utils/Errors.h>
+
+#include "jni_wrappers.h"
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+static struct {
+ jclass clazz;
+ jfieldID primaryDisplayId;
+ jfieldID displayNodes;
+} gDisplayTopologyGraphClassInfo;
+
+static struct {
+ jclass clazz;
+ jfieldID displayId;
+ jfieldID adjacentDisplays;
+} gDisplayTopologyGraphNodeClassInfo;
+
+static struct {
+ jclass clazz;
+ jfieldID displayId;
+ jfieldID position;
+ jfieldID offsetPx;
+} gDisplayTopologyGraphAdjacentDisplayClassInfo;
+
+// ----------------------------------------------------------------------------
+
+status_t android_hardware_display_DisplayTopologyAdjacentDisplay_toNative(
+ JNIEnv* env, jobject adjacentDisplayObj, DisplayTopologyAdjacentDisplay* adjacentDisplay) {
+ adjacentDisplay->displayId = ui::LogicalDisplayId{
+ env->GetIntField(adjacentDisplayObj,
+ gDisplayTopologyGraphAdjacentDisplayClassInfo.displayId)};
+ adjacentDisplay->position = static_cast<DisplayTopologyPosition>(
+ env->GetIntField(adjacentDisplayObj,
+ gDisplayTopologyGraphAdjacentDisplayClassInfo.position));
+ adjacentDisplay->offsetPx =
+ env->GetFloatField(adjacentDisplayObj,
+ gDisplayTopologyGraphAdjacentDisplayClassInfo.offsetPx);
+ return OK;
+}
+
+status_t android_hardware_display_DisplayTopologyGraphNode_toNative(
+ JNIEnv* env, jobject nodeObj,
+ std::unordered_map<ui::LogicalDisplayId, std::vector<DisplayTopologyAdjacentDisplay>>&
+ graph) {
+ ui::LogicalDisplayId displayId = ui::LogicalDisplayId{
+ env->GetIntField(nodeObj, gDisplayTopologyGraphNodeClassInfo.displayId)};
+
+ jobjectArray adjacentDisplaysArray = static_cast<jobjectArray>(
+ env->GetObjectField(nodeObj, gDisplayTopologyGraphNodeClassInfo.adjacentDisplays));
+
+ if (adjacentDisplaysArray) {
+ jsize length = env->GetArrayLength(adjacentDisplaysArray);
+ for (jsize i = 0; i < length; i++) {
+ ScopedLocalRef<jobject>
+ adjacentDisplayObj(env, env->GetObjectArrayElement(adjacentDisplaysArray, i));
+ if (NULL != adjacentDisplayObj.get()) {
+ break; // found null element indicating end of used portion of the array
+ }
+
+ DisplayTopologyAdjacentDisplay adjacentDisplay;
+ android_hardware_display_DisplayTopologyAdjacentDisplay_toNative(env,
+ adjacentDisplayObj
+ .get(),
+ &adjacentDisplay);
+ graph[displayId].push_back(adjacentDisplay);
+ }
+ }
+ return OK;
+}
+
+DisplayTopologyGraph android_hardware_display_DisplayTopologyGraph_toNative(JNIEnv* env,
+ jobject topologyObj) {
+ DisplayTopologyGraph topology;
+ topology.primaryDisplayId = ui::LogicalDisplayId{
+ env->GetIntField(topologyObj, gDisplayTopologyGraphClassInfo.primaryDisplayId)};
+
+ jobjectArray nodesArray = static_cast<jobjectArray>(
+ env->GetObjectField(topologyObj, gDisplayTopologyGraphClassInfo.displayNodes));
+
+ if (nodesArray) {
+ jsize length = env->GetArrayLength(nodesArray);
+ for (jsize i = 0; i < length; i++) {
+ ScopedLocalRef<jobject> nodeObj(env, env->GetObjectArrayElement(nodesArray, i));
+ if (NULL != nodeObj.get()) {
+ break; // found null element indicating end of used portion of the array
+ }
+
+ android_hardware_display_DisplayTopologyGraphNode_toNative(env, nodeObj.get(),
+ topology.graph);
+ }
+ }
+ return topology;
+}
+
+// ----------------------------------------------------------------------------
+
+int register_android_hardware_display_DisplayTopology(JNIEnv* env) {
+ jclass graphClazz = FindClassOrDie(env, "android/hardware/display/DisplayTopologyGraph");
+ gDisplayTopologyGraphClassInfo.clazz = MakeGlobalRefOrDie(env, graphClazz);
+
+ gDisplayTopologyGraphClassInfo.primaryDisplayId =
+ GetFieldIDOrDie(env, gDisplayTopologyGraphClassInfo.clazz, "primaryDisplayId", "I");
+ gDisplayTopologyGraphClassInfo.displayNodes =
+ GetFieldIDOrDie(env, gDisplayTopologyGraphClassInfo.clazz, "displayNodes",
+ "[Landroid/hardware/display/DisplayTopologyGraph$DisplayNode;");
+
+ jclass displayNodeClazz =
+ FindClassOrDie(env, "android/hardware/display/DisplayTopologyGraph$DisplayNode");
+ gDisplayTopologyGraphNodeClassInfo.clazz = MakeGlobalRefOrDie(env, displayNodeClazz);
+ gDisplayTopologyGraphNodeClassInfo.displayId =
+ GetFieldIDOrDie(env, gDisplayTopologyGraphNodeClassInfo.clazz, "displayId", "I");
+ gDisplayTopologyGraphNodeClassInfo.adjacentDisplays =
+ GetFieldIDOrDie(env, gDisplayTopologyGraphNodeClassInfo.clazz, "adjacentDisplays",
+ "[Landroid/hardware/display/DisplayTopologyGraph$AdjacentDisplay;");
+
+ jclass adjacentDisplayClazz =
+ FindClassOrDie(env, "android/hardware/display/DisplayTopologyGraph$AdjacentDisplay");
+ gDisplayTopologyGraphAdjacentDisplayClassInfo.clazz =
+ MakeGlobalRefOrDie(env, adjacentDisplayClazz);
+ gDisplayTopologyGraphAdjacentDisplayClassInfo.displayId =
+ GetFieldIDOrDie(env, gDisplayTopologyGraphAdjacentDisplayClassInfo.clazz, "displayId",
+ "I");
+ gDisplayTopologyGraphAdjacentDisplayClassInfo.position =
+ GetFieldIDOrDie(env, gDisplayTopologyGraphAdjacentDisplayClassInfo.clazz, "position",
+ "I");
+ gDisplayTopologyGraphAdjacentDisplayClassInfo.offsetPx =
+ GetFieldIDOrDie(env, gDisplayTopologyGraphAdjacentDisplayClassInfo.clazz, "offsetPx",
+ "F");
+ return 0;
+}
+
+} // namespace android
diff --git a/core/jni/android_hardware_display_DisplayTopology.h b/core/jni/android_hardware_display_DisplayTopology.h
new file mode 100644
index 0000000..390191f
--- /dev/null
+++ b/core/jni/android_hardware_display_DisplayTopology.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <input/DisplayTopologyGraph.h>
+
+#include "jni.h"
+
+namespace android {
+
+/**
+ * Copies the contents of a DVM DisplayTopology object to a new native DisplayTopology instance.
+ * Returns DisplayTopology.
+ */
+extern DisplayTopologyGraph android_hardware_display_DisplayTopologyGraph_toNative(
+ JNIEnv* env, jobject eventObj);
+
+} // namespace android
diff --git a/core/res/res/values-watch/config_material.xml b/core/res/res/values-watch/config_material.xml
index 8e9693a..73a7c09 100644
--- a/core/res/res/values-watch/config_material.xml
+++ b/core/res/res/values-watch/config_material.xml
@@ -69,4 +69,14 @@
<integer name="config_motionExpressiveSlowSpatialStiffness">200</integer>
<item name="config_motionExpressiveSlowEffectDamping" format="float" type="dimen">1.0</item>
<integer name="config_motionExpressiveSlowEffectStiffness">260</integer>
+
+ <!--
+ Material rounded corner configs
+ Values from https://carbon.googleplex.com/wear-m3/tokens/designSystems/70fbaa4f7722a3d1/tokenSets/4fa2518eaeaf65eb
+ -->
+ <dimen name="config_shapeCornerRadiusXsmall">4dp</dimen>
+ <dimen name="config_shapeCornerRadiusSmall">8dp</dimen>
+ <dimen name="config_shapeCornerRadiusMedium">18dp</dimen>
+ <dimen name="config_shapeCornerRadiusLarge">26dp</dimen>
+ <dimen name="config_shapeCornerRadiusXlarge">36dp</dimen>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 390fbea..13c125c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2372,7 +2372,7 @@
<string name="default_sms_application" translatable="false">com.android.messaging</string>
<!-- Flag indicating whether the current device supports "Ask every time" for sms-->
- <bool name="config_sms_ask_every_time_support">true</bool>
+ <bool name="config_sms_ask_every_time_support">false</bool>
<!-- Flag indicating whether the current device allows acknowledgement of SIM operation like
SM-PP or saving SMS to SIM can be done via the IMS interfaces.
@@ -7276,4 +7276,9 @@
<!-- List of protected packages that require biometric authentication for modification
(Disable, force-stop or uninstalling updates). -->
<string-array name="config_biometric_protected_package_names" translatable="false" />
+
+ <!-- Package name of the on-device intelligent processor for vendor specific
+ features. Examples include the search functionality or the app
+ predictor. -->
+ <string name="config_systemVendorIntelligence" translatable="false"></string>
</resources>
diff --git a/core/res/res/values/config_material.xml b/core/res/res/values/config_material.xml
index 6034f9c..648fe90 100644
--- a/core/res/res/values/config_material.xml
+++ b/core/res/res/values/config_material.xml
@@ -75,4 +75,14 @@
<integer name="config_motionExpressiveSlowSpatialStiffness">200</integer>
<item name="config_motionExpressiveSlowEffectDamping" format="float" type="dimen">1.0</item>
<integer name="config_motionExpressiveSlowEffectStiffness">800</integer>
+
+ <!--
+ Material rounded corner configs
+ Values from https://carbon.googleplex.com/google-material-3/tokens/designSystems/20543ce18892f7d9/tokenSets/21c40db4e4f5af15
+ -->
+ <dimen name="config_shapeCornerRadiusXsmall">4dp</dimen>
+ <dimen name="config_shapeCornerRadiusSmall">8dp</dimen>
+ <dimen name="config_shapeCornerRadiusMedium">12dp</dimen>
+ <dimen name="config_shapeCornerRadiusLarge">16dp</dimen>
+ <dimen name="config_shapeCornerRadiusXlarge">28dp</dimen>
</resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 90f1b8a..6c73b0c 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -154,6 +154,9 @@
<!-- @FlaggedApi(android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_PLATFORM_API_ENABLED)
@hide @SystemApi -->
<public name="config_defaultReservedForTestingProfileGroupExclusivity" />
+ <!-- @FlaggedApi(android.permission.flags.Flags.FLAG_SYSTEM_VENDOR_INTELLIGENCE_ROLE_ENABLED)
+ @hide @SystemApi -->
+ <public name="config_systemVendorIntelligence" />
</staging-public-group>
<staging-public-group type="dimen" first-id="0x01b30000">
@@ -181,6 +184,16 @@
<public name="config_motionExpressiveSlowSpatialDamping"/>
<!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
<public name="config_motionExpressiveSlowEffectDamping"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_SHAPE_TOKENS)-->
+ <public name="config_shapeCornerRadiusXsmall"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_SHAPE_TOKENS)-->
+ <public name="config_shapeCornerRadiusSmall"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_SHAPE_TOKENS)-->
+ <public name="config_shapeCornerRadiusMedium"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_SHAPE_TOKENS)-->
+ <public name="config_shapeCornerRadiusLarge"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_SHAPE_TOKENS)-->
+ <public name="config_shapeCornerRadiusXlarge"/>
</staging-public-group>
<staging-public-group type="color" first-id="0x01b20000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 2671ff9..7e8a2e9 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5878,4 +5878,12 @@
<!-- List of protected packages that require biometric authentication for modification -->
<java-symbol type="array" name="config_biometric_protected_package_names" />
+ <!-- Material shape spec config tokens -->
+ <java-symbol type="dimen" name="config_shapeCornerRadiusXsmall"/>
+ <java-symbol type="dimen" name="config_shapeCornerRadiusSmall"/>
+ <java-symbol type="dimen" name="config_shapeCornerRadiusMedium"/>
+ <java-symbol type="dimen" name="config_shapeCornerRadiusLarge"/>
+ <java-symbol type="dimen" name="config_shapeCornerRadiusXlarge"/>
+
+
</resources>
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index bd27337..177c7f0 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -740,20 +740,5 @@
assertEquals(null, cache.query(30));
// The recompute is 4 because nulls were not cached.
assertEquals(4, cache.getRecomputeCount());
-
- // Verify that the default is not to cache nulls.
- cache = new TestCache(new Args(MODULE_TEST)
- .maxEntries(4).api("testCachingNulls"),
- new TestQuery());
- cache.invalidateCache();
- assertEquals("foo1", cache.query(1));
- assertEquals("foo2", cache.query(2));
- assertEquals(null, cache.query(30));
- assertEquals(3, cache.getRecomputeCount());
- assertEquals("foo1", cache.query(1));
- assertEquals("foo2", cache.query(2));
- assertEquals(null, cache.query(30));
- // The recompute is 4 because nulls were not cached.
- assertEquals(4, cache.getRecomputeCount());
}
}
diff --git a/core/tests/coretests/src/android/os/IpcDataCacheTest.java b/core/tests/coretests/src/android/os/IpcDataCacheTest.java
index 523b1b0..e14608a 100644
--- a/core/tests/coretests/src/android/os/IpcDataCacheTest.java
+++ b/core/tests/coretests/src/android/os/IpcDataCacheTest.java
@@ -16,19 +16,13 @@
package android.os;
-import static android.app.Flags.FLAG_PIC_CACHE_NULLS;
-import static android.app.Flags.FLAG_PIC_ISOLATE_CACHE_BY_UID;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
-import android.app.PropertyInvalidatedCache;
-import android.app.PropertyInvalidatedCache.Args;
import android.multiuser.Flags;
import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.ravenwood.RavenwoodRule;
-import android.os.IpcDataCache;
import androidx.test.filters.SmallTest;
@@ -293,12 +287,7 @@
@Override
public String apply(Integer qv) {
mRecomputeCount += 1;
- // Special case for testing caches of nulls. Integers in the range 30-40 return null.
- if (qv >= 30 && qv < 40) {
- return null;
- } else {
- return "foo" + qv.toString();
- }
+ return "foo" + qv.toString();
}
int getRecomputeCount() {
@@ -417,16 +406,31 @@
}
@Test
- public void testConfigDisable() {
- // Create a set of caches based on a set of chained configs.
+ public void testConfig() {
IpcDataCache.Config a = new IpcDataCache.Config(8, MODULE, "apiA");
TestCache ac = new TestCache(a);
+ assertEquals(8, a.maxEntries());
+ assertEquals(MODULE, a.module());
+ assertEquals("apiA", a.api());
+ assertEquals("apiA", a.name());
IpcDataCache.Config b = new IpcDataCache.Config(a, "apiB");
TestCache bc = new TestCache(b);
+ assertEquals(8, b.maxEntries());
+ assertEquals(MODULE, b.module());
+ assertEquals("apiB", b.api());
+ assertEquals("apiB", b.name());
IpcDataCache.Config c = new IpcDataCache.Config(a, "apiC", "nameC");
TestCache cc = new TestCache(c);
+ assertEquals(8, c.maxEntries());
+ assertEquals(MODULE, c.module());
+ assertEquals("apiC", c.api());
+ assertEquals("nameC", c.name());
IpcDataCache.Config d = a.child("nameD");
TestCache dc = new TestCache(d);
+ assertEquals(8, d.maxEntries());
+ assertEquals(MODULE, d.module());
+ assertEquals("apiA", d.api());
+ assertEquals("nameD", d.name());
a.disableForCurrentProcess();
assertEquals(ac.isDisabled(), true);
@@ -445,7 +449,6 @@
assertEquals(ec.isDisabled(), true);
}
-
// Verify that invalidating the cache from an app process would fail due to lack of permissions.
@Test
@android.platform.test.annotations.DisabledOnRavenwood(
@@ -504,47 +507,4 @@
// Re-enable test mode (so that the cleanup for the test does not throw).
IpcDataCache.setTestMode(true);
}
-
- @RequiresFlagsEnabled(FLAG_PIC_CACHE_NULLS)
- @Test
- public void testCachingNulls() {
- IpcDataCache.Config c =
- new IpcDataCache.Config(4, IpcDataCache.MODULE_TEST, "testCachingNulls");
- TestCache cache;
- cache = new TestCache(c.cacheNulls(true));
- cache.invalidateCache();
- assertEquals("foo1", cache.query(1));
- assertEquals("foo2", cache.query(2));
- assertEquals(null, cache.query(30));
- assertEquals(3, cache.getRecomputeCount());
- assertEquals("foo1", cache.query(1));
- assertEquals("foo2", cache.query(2));
- assertEquals(null, cache.query(30));
- assertEquals(3, cache.getRecomputeCount());
-
- cache = new TestCache(c.cacheNulls(false));
- cache.invalidateCache();
- assertEquals("foo1", cache.query(1));
- assertEquals("foo2", cache.query(2));
- assertEquals(null, cache.query(30));
- assertEquals(3, cache.getRecomputeCount());
- assertEquals("foo1", cache.query(1));
- assertEquals("foo2", cache.query(2));
- assertEquals(null, cache.query(30));
- // The recompute is 4 because nulls were not cached.
- assertEquals(4, cache.getRecomputeCount());
-
- // Verify that the default is not to cache nulls.
- cache = new TestCache(c);
- cache.invalidateCache();
- assertEquals("foo1", cache.query(1));
- assertEquals("foo2", cache.query(2));
- assertEquals(null, cache.query(30));
- assertEquals(3, cache.getRecomputeCount());
- assertEquals("foo1", cache.query(1));
- assertEquals("foo2", cache.query(2));
- assertEquals(null, cache.query(30));
- // The recompute is 4 because nulls were not cached.
- assertEquals(4, cache.getRecomputeCount());
- }
}
diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java
index c3bd065..108f5ba 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java
@@ -211,6 +211,102 @@
}
@Test
+ public void testCalculateInsets_partialSideIntersection_leftCenter() {
+ mSource.setFrame(new Rect(0, 0, 100, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(0, 100, 500, 400), false);
+ assertEquals(Insets.of(100, 0, 0, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_leftTop() {
+ mSource.setFrame(new Rect(0, 0, 100, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(0, -100, 500, 400), false);
+ assertEquals(Insets.of(100, 0, 0, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_leftBottom() {
+ mSource.setFrame(new Rect(0, 0, 100, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(0, 100, 500, 600), false);
+ assertEquals(Insets.of(100, 0, 0, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_topCenter() {
+ mSource.setFrame(new Rect(0, 0, 500, 100));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(-100, 0, 600, 500), false);
+ assertEquals(Insets.of(0, 100, 0, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_topLeft() {
+ mSource.setFrame(new Rect(0, 0, 500, 100));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(-100, 0, 400, 500), false);
+ assertEquals(Insets.of(0, 100, 0, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_topRight() {
+ mSource.setFrame(new Rect(0, 0, 500, 100));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(100, 0, 600, 500), false);
+ assertEquals(Insets.of(0, 100, 0, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_rightCenter() {
+ mSource.setFrame(new Rect(400, 0, 500, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(0, 100, 500, 400), false);
+ assertEquals(Insets.of(0, 0, 100, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_rightTop() {
+ mSource.setFrame(new Rect(400, 0, 500, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(0, -100, 500, 400), false);
+ assertEquals(Insets.of(0, 0, 100, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_rightBottom() {
+ mSource.setFrame(new Rect(400, 0, 500, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(0, 100, 500, 600), false);
+ assertEquals(Insets.of(0, 0, 100, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_bottomCenter() {
+ mSource.setFrame(new Rect(0, 400, 500, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(-100, 0, 600, 500), false);
+ assertEquals(Insets.of(0, 0, 0, 100), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_bottomLeft() {
+ mSource.setFrame(new Rect(0, 400, 500, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(-100, 0, 400, 500), false);
+ assertEquals(Insets.of(0, 0, 0, 100), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_bottomRight() {
+ mSource.setFrame(new Rect(0, 400, 500, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(100, 0, 600, 500), false);
+ assertEquals(Insets.of(0, 0, 0, 100), insets);
+ }
+
+ @Test
public void testCalculateVisibleInsets_override() {
mSource.setFrame(new Rect(0, 0, 500, 100));
mSource.setVisibleFrame(new Rect(0, 0, 500, 200));
diff --git a/core/tests/coretests/src/android/view/ViewGroupTest.java b/core/tests/coretests/src/android/view/ViewGroupTest.java
index ae3ad36..43c404e 100644
--- a/core/tests/coretests/src/android/view/ViewGroupTest.java
+++ b/core/tests/coretests/src/android/view/ViewGroupTest.java
@@ -213,6 +213,35 @@
assertTrue(autofillableViews.containsAll(Arrays.asList(viewA, viewC)));
}
+ @Test
+ public void testMeasureCache() {
+ final int spec1 = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.AT_MOST);
+ final int spec2 = View.MeasureSpec.makeMeasureSpec(50, View.MeasureSpec.AT_MOST);
+ final Context context = getInstrumentation().getContext();
+ final View child = new View(context);
+ final TestView parent = new TestView(context, 0);
+ parent.addView(child);
+
+ child.setPadding(1, 2, 3, 4);
+ parent.measure(spec1, spec1);
+ assertEquals(4, parent.getMeasuredWidth());
+ assertEquals(6, parent.getMeasuredHeight());
+
+ child.setPadding(5, 6, 7, 8);
+ parent.measure(spec2, spec2);
+ assertEquals(12, parent.getMeasuredWidth());
+ assertEquals(14, parent.getMeasuredHeight());
+
+ // This ends the state of forceLayout.
+ parent.layout(0, 0, 50, 50);
+
+ // The cached values should be cleared after the new setPadding is called. And the measured
+ // width and height should be up-to-date.
+ parent.measure(spec1, spec1);
+ assertEquals(12, parent.getMeasuredWidth());
+ assertEquals(14, parent.getMeasuredHeight());
+ }
+
private static void getUnobscuredTouchableRegion(Region outRegion, View view) {
outRegion.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
final ViewParent parent = view.getParent();
@@ -240,6 +269,19 @@
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// We don't layout this view.
}
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int measuredWidth = 0;
+ int measuredHeight = 0;
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ measuredWidth += child.getPaddingLeft() + child.getPaddingRight();
+ measuredHeight += child.getPaddingTop() + child.getPaddingBottom();
+ }
+ setMeasuredDimension(measuredWidth, measuredHeight);
+ }
}
public static class AutofillableTestView extends TestView {
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 21ec84d..9e2d23b 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -266,6 +266,8 @@
<dimen name="bubble_bar_expanded_view_handle_height">4dp</dimen>
<!-- Width of the expanded bubble bar view shown when the bubble is expanded. -->
<dimen name="bubble_bar_expanded_view_width">412dp</dimen>
+ <!-- Offset of the expanded view when it starts sliding in as part of the switch animation -->
+ <dimen name="bubble_bar_expanded_view_switch_offset">48dp</dimen>
<!-- Minimum width of the bubble bar manage menu. -->
<dimen name="bubble_bar_manage_menu_min_width">200dp</dimen>
<!-- Size of the dismiss icon in the bubble bar manage menu. -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index a313bd0..4d7c7fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.bubbles.bar;
import static android.view.View.ALPHA;
+import static android.view.View.INVISIBLE;
import static android.view.View.SCALE_X;
import static android.view.View.SCALE_Y;
import static android.view.View.TRANSLATION_X;
@@ -25,6 +26,7 @@
import static android.view.View.Y;
import static com.android.wm.shell.bubbles.bar.BubbleBarExpandedView.CORNER_RADIUS;
+import static com.android.wm.shell.bubbles.bar.BubbleBarExpandedView.TASK_VIEW_ALPHA;
import static com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED;
import static com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED_DECELERATE;
@@ -32,7 +34,6 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
@@ -42,11 +43,12 @@
import androidx.annotation.Nullable;
+import com.android.app.animation.Interpolators;
+import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleViewProvider;
import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix;
-import com.android.wm.shell.shared.animation.Interpolators;
import com.android.wm.shell.shared.animation.PhysicsAnimator;
import com.android.wm.shell.shared.magnetictarget.MagnetizedObject.MagneticTarget;
@@ -59,7 +61,7 @@
private static final float EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT = 0.1f;
private static final float EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT = .75f;
- private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150;
+ private static final int EXPANDED_VIEW_EXPAND_ALPHA_DURATION = 150;
private static final int EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION = 400;
private static final int EXPANDED_VIEW_ANIMATE_TO_REST_DURATION = 400;
private static final int EXPANDED_VIEW_DISMISS_DURATION = 250;
@@ -72,6 +74,17 @@
private static final float DISMISS_VIEW_SCALE = 1.25f;
private static final int HANDLE_ALPHA_ANIMATION_DURATION = 100;
+ private static final float SWITCH_OUT_SCALE = 0.97f;
+ private static final long SWITCH_OUT_SCALE_DURATION = 200L;
+ private static final long SWITCH_OUT_ALPHA_DURATION = 100L;
+ private static final long SWITCH_OUT_HANDLE_ALPHA_DURATION = 50L;
+ private static final long SWITCH_IN_ANIM_DELAY = 50L;
+ private static final long SWITCH_IN_TX_DURATION = 350L;
+ private static final long SWITCH_IN_ALPHA_DURATION = 50L;
+ // Keep this handle alpha delay at least as long as alpha animation for both expanded views.
+ private static final long SWITCH_IN_HANDLE_ALPHA_DELAY = 150L;
+ private static final long SWITCH_IN_HANDLE_ALPHA_DURATION = 100L;
+
/** Spring config for the expanded view scale-in animation. */
private final PhysicsAnimator.SpringConfig mScaleInSpringConfig =
new PhysicsAnimator.SpringConfig(300f, 0.9f);
@@ -80,68 +93,24 @@
private final PhysicsAnimator.SpringConfig mScaleOutSpringConfig =
new PhysicsAnimator.SpringConfig(900f, 1f);
+ private final int mSwitchAnimPositionOffset;
+
/** Matrix used to scale the expanded view container with a given pivot point. */
private final AnimatableScaleMatrix mExpandedViewContainerMatrix = new AnimatableScaleMatrix();
- /** Animator for animating the expanded view's alpha (including the TaskView inside it). */
- private final ValueAnimator mExpandedViewAlphaAnimator = ValueAnimator.ofFloat(0f, 1f);
-
@Nullable
- private Animator mRunningDragAnimator;
+ private Animator mRunningAnimator;
- private final Context mContext;
- private final BubbleBarLayerView mLayerView;
private final BubblePositioner mPositioner;
private final int[] mTmpLocation = new int[2];
+ // TODO(b/381936992): remove expanded bubble state from this helper class
private BubbleViewProvider mExpandedBubble;
- private boolean mIsExpanded = false;
- public BubbleBarAnimationHelper(Context context,
- BubbleBarLayerView bubbleBarLayerView,
- BubblePositioner positioner) {
- mContext = context;
- mLayerView = bubbleBarLayerView;
+ public BubbleBarAnimationHelper(Context context, BubblePositioner positioner) {
mPositioner = positioner;
-
- mExpandedViewAlphaAnimator.setDuration(EXPANDED_VIEW_ALPHA_ANIMATION_DURATION);
- mExpandedViewAlphaAnimator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
- mExpandedViewAlphaAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- BubbleBarExpandedView bbev = getExpandedView();
- if (bbev != null) {
- // We need to be Z ordered on top in order for alpha animations to work.
- bbev.setSurfaceZOrderedOnTop(true);
- bbev.setAnimating(true);
- }
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- BubbleBarExpandedView bbev = getExpandedView();
- if (bbev != null) {
- // The surface needs to be Z ordered on top for alpha values to work on the
- // TaskView, and if we're temporarily hidden, we are still on the screen
- // with alpha = 0f until we animate back. Stay Z ordered on top so the alpha
- // = 0f remains in effect.
- if (mIsExpanded) {
- bbev.setSurfaceZOrderedOnTop(false);
- }
-
- bbev.setContentVisibility(mIsExpanded);
- bbev.setAnimating(false);
- }
- }
- });
- mExpandedViewAlphaAnimator.addUpdateListener(valueAnimator -> {
- BubbleBarExpandedView bbev = getExpandedView();
- if (bbev != null) {
- float alpha = (float) valueAnimator.getAnimatedValue();
- bbev.setTaskViewAlpha(alpha);
- bbev.setAlpha(alpha);
- }
- });
+ mSwitchAnimPositionOffset = context.getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_switch_offset);
}
/**
@@ -154,18 +123,11 @@
if (bbev == null) {
return;
}
- mIsExpanded = true;
mExpandedViewContainerMatrix.setScaleX(0f);
mExpandedViewContainerMatrix.setScaleY(0f);
- updateExpandedView();
- bbev.setAnimating(true);
- bbev.setSurfaceZOrderedOnTop(true);
- bbev.setContentVisibility(false);
- bbev.setAlpha(0f);
- bbev.setTaskViewAlpha(0f);
- bbev.setVisibility(VISIBLE);
+ prepareForAnimateIn(bbev);
setScaleFromBubbleBar(mExpandedViewContainerMatrix,
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT);
@@ -173,7 +135,16 @@
bbev.setAnimationMatrix(mExpandedViewContainerMatrix);
bbev.animateExpansionWhenTaskViewVisible(() -> {
- mExpandedViewAlphaAnimator.start();
+ ObjectAnimator alphaAnim = createAlphaAnimator(bbev, /* visible= */ true);
+ alphaAnim.setDuration(EXPANDED_VIEW_EXPAND_ALPHA_DURATION);
+ alphaAnim.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
+ alphaAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ bbev.setAnimating(false);
+ }
+ });
+ startNewAnimator(alphaAnim);
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
@@ -188,7 +159,7 @@
})
.withEndActions(() -> {
bbev.setAnimationMatrix(null);
- updateExpandedView();
+ updateExpandedView(bbev);
if (afterAnimation != null) {
afterAnimation.run();
}
@@ -197,13 +168,24 @@
});
}
+ private void prepareForAnimateIn(BubbleBarExpandedView bbev) {
+ bbev.setAnimating(true);
+ updateExpandedView(bbev);
+ // We need to be Z ordered on top in order for taskView alpha to work.
+ // It is also set when the alpha animation starts, but needs to be set here to too avoid
+ // flickers.
+ bbev.setSurfaceZOrderedOnTop(true);
+ bbev.setTaskViewAlpha(0f);
+ bbev.setContentVisibility(false);
+ bbev.setVisibility(VISIBLE);
+ }
+
/**
* Collapses the currently expanded bubble.
*
* @param endRunnable a runnable to run at the end of the animation.
*/
public void animateCollapse(Runnable endRunnable) {
- mIsExpanded = false;
final BubbleBarExpandedView bbev = getExpandedView();
if (bbev == null) {
Log.w(TAG, "Trying to animate collapse without a bubble");
@@ -214,6 +196,19 @@
setScaleFromBubbleBar(mExpandedViewContainerMatrix, 1f);
+ bbev.setAnimating(true);
+
+ ObjectAnimator alphaAnim = createAlphaAnimator(bbev, /* visible= */ false);
+ alphaAnim.setDuration(EXPANDED_VIEW_EXPAND_ALPHA_DURATION);
+ alphaAnim.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
+ alphaAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ bbev.setAnimating(false);
+ }
+ });
+ startNewAnimator(alphaAnim);
+
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
.spring(AnimatableScaleMatrix.SCALE_X,
@@ -234,7 +229,6 @@
}
})
.start();
- mExpandedViewAlphaAnimator.reverse();
}
private void setScaleFromBubbleBar(AnimatableScaleMatrix matrix, float scale) {
@@ -246,6 +240,142 @@
}
/**
+ * Animate between two bubble views using a switch animation
+ *
+ * @param fromBubble bubble to hide
+ * @param toBubble bubble to show
+ * @param afterAnimation optional runnable after animation finishes
+ */
+ public void animateSwitch(BubbleViewProvider fromBubble, BubbleViewProvider toBubble,
+ @Nullable Runnable afterAnimation) {
+ /*
+ * Switch animation
+ *
+ * |.....................fromBubble scale to 0.97.....................|
+ * |fromBubble handle alpha 0|....fromBubble alpha to 0.....| |
+ * 0-------------------------50-----------------------100---150--------200----------250--400
+ * |..toBubble alpha to 1...| |toBubble handle alpha 1| |
+ * |................toBubble position +/-48 to 0...............|
+ */
+
+ mExpandedBubble = toBubble;
+ final BubbleBarExpandedView toBbev = toBubble.getBubbleBarExpandedView();
+ final BubbleBarExpandedView fromBbev = fromBubble.getBubbleBarExpandedView();
+ if (toBbev == null || fromBbev == null) {
+ return;
+ }
+
+ fromBbev.setAnimating(true);
+
+ prepareForAnimateIn(toBbev);
+ final float endTx = toBbev.getTranslationX();
+ final float startTx = getSwitchAnimationInitialTx(endTx);
+ toBbev.setTranslationX(startTx);
+ toBbev.getHandleView().setAlpha(0f);
+
+ toBbev.animateExpansionWhenTaskViewVisible(() -> {
+ AnimatorSet switchAnim = new AnimatorSet();
+ switchAnim.playTogether(switchOutAnimator(fromBbev), switchInAnimator(toBbev, endTx));
+
+ switchAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (afterAnimation != null) {
+ afterAnimation.run();
+ }
+ }
+ });
+ startNewAnimator(switchAnim);
+ });
+ }
+
+ private float getSwitchAnimationInitialTx(float endTx) {
+ if (mPositioner.isBubbleBarOnLeft()) {
+ return endTx - mSwitchAnimPositionOffset;
+ } else {
+ return endTx + mSwitchAnimPositionOffset;
+ }
+ }
+
+ private Animator switchOutAnimator(BubbleBarExpandedView bbev) {
+ setPivotToCenter(bbev);
+ AnimatorSet scaleAnim = new AnimatorSet();
+ scaleAnim.playTogether(
+ ObjectAnimator.ofFloat(bbev, SCALE_X, SWITCH_OUT_SCALE),
+ ObjectAnimator.ofFloat(bbev, SCALE_Y, SWITCH_OUT_SCALE)
+ );
+ scaleAnim.setInterpolator(Interpolators.ACCELERATE);
+ scaleAnim.setDuration(SWITCH_OUT_SCALE_DURATION);
+
+ ObjectAnimator alphaAnim = createAlphaAnimator(bbev, /* visible= */ false);
+ alphaAnim.setStartDelay(SWITCH_OUT_HANDLE_ALPHA_DURATION);
+ alphaAnim.setDuration(SWITCH_OUT_ALPHA_DURATION);
+
+ ObjectAnimator handleAlphaAnim = ObjectAnimator.ofFloat(bbev.getHandleView(), ALPHA, 0f)
+ .setDuration(SWITCH_OUT_HANDLE_ALPHA_DURATION);
+
+ AnimatorSet animator = new AnimatorSet();
+ animator.playTogether(scaleAnim, alphaAnim, handleAlphaAnim);
+
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ bbev.setAnimating(false);
+ }
+ });
+ return animator;
+ }
+
+ private Animator switchInAnimator(BubbleBarExpandedView bbev, float restingTx) {
+ ObjectAnimator positionAnim = ObjectAnimator.ofFloat(bbev, TRANSLATION_X, restingTx);
+ positionAnim.setInterpolator(Interpolators.EMPHASIZED_DECELERATE);
+ positionAnim.setStartDelay(SWITCH_IN_ANIM_DELAY);
+ positionAnim.setDuration(SWITCH_IN_TX_DURATION);
+
+ // Animate alpha directly to have finer control over surface z-ordering
+ ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(bbev, TASK_VIEW_ALPHA, 1f);
+ alphaAnim.setStartDelay(SWITCH_IN_ANIM_DELAY);
+ alphaAnim.setDuration(SWITCH_IN_ALPHA_DURATION);
+ alphaAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ bbev.setSurfaceZOrderedOnTop(true);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ bbev.setContentVisibility(true);
+ // The outgoing expanded view alpha animation is still in progress.
+ // Do not reset the surface z-order as otherwise the outgoing expanded view is
+ // placed on top.
+ }
+ });
+
+ ObjectAnimator handleAlphaAnim = ObjectAnimator.ofFloat(bbev.getHandleView(), ALPHA, 1f);
+ handleAlphaAnim.setStartDelay(SWITCH_IN_HANDLE_ALPHA_DELAY);
+ handleAlphaAnim.setDuration(SWITCH_IN_HANDLE_ALPHA_DURATION);
+ handleAlphaAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ bbev.setSurfaceZOrderedOnTop(false);
+ bbev.setAnimating(false);
+ }
+ });
+
+ AnimatorSet animator = new AnimatorSet();
+ animator.playTogether(positionAnim, alphaAnim, handleAlphaAnim);
+
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ updateExpandedView(bbev);
+ }
+ });
+ return animator;
+ }
+
+
+ /**
* Animate the expanded bubble when it is being dragged
*/
public void animateStartDrag() {
@@ -273,7 +403,7 @@
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(contentAnim, handleAnim);
animatorSet.addListener(new DragAnimatorListenerAdapter(bbev));
- startNewDragAnimation(animatorSet);
+ startNewAnimator(animatorSet);
}
/**
@@ -282,7 +412,6 @@
* @param endRunnable a runnable to run at the end of the animation
*/
public void animateDismiss(Runnable endRunnable) {
- mIsExpanded = false;
final BubbleBarExpandedView bbev = getExpandedView();
if (bbev == null) {
Log.w(TAG, "Trying to animate dismiss without a bubble");
@@ -335,7 +464,7 @@
bbev.setDragging(false);
}
});
- startNewDragAnimation(animatorSet);
+ startNewAnimator(animatorSet);
}
/**
@@ -409,7 +538,7 @@
}
}
});
- startNewDragAnimation(animatorSet);
+ startNewAnimator(animatorSet);
}
/**
@@ -435,7 +564,7 @@
animatorSet.setDuration(EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION).setInterpolator(
EMPHASIZED_DECELERATE);
animatorSet.addListener(new DragAnimatorListenerAdapter(bbev));
- startNewDragAnimation(animatorSet);
+ startNewAnimator(animatorSet);
}
/**
@@ -443,14 +572,15 @@
*/
public void cancelAnimations() {
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
- mExpandedViewAlphaAnimator.cancel();
BubbleBarExpandedView bbev = getExpandedView();
if (bbev != null) {
bbev.animate().cancel();
}
- if (mRunningDragAnimator != null) {
- mRunningDragAnimator.cancel();
- mRunningDragAnimator = null;
+ if (mRunningAnimator != null) {
+ if (mRunningAnimator.isRunning()) {
+ mRunningAnimator.cancel();
+ }
+ mRunningAnimator = null;
}
}
@@ -462,8 +592,7 @@
return null;
}
- private void updateExpandedView() {
- BubbleBarExpandedView bbev = getExpandedView();
+ private void updateExpandedView(BubbleBarExpandedView bbev) {
if (bbev == null) {
Log.w(TAG, "Trying to update the expanded view without a bubble");
return;
@@ -477,6 +606,8 @@
bbev.setLayoutParams(lp);
bbev.setX(position.x);
bbev.setY(position.y);
+ bbev.setScaleX(1f);
+ bbev.setScaleY(1f);
bbev.updateLocation();
bbev.maybeShowOverflow();
}
@@ -500,18 +631,54 @@
return new Size(width, height);
}
- private void startNewDragAnimation(Animator animator) {
+ private void startNewAnimator(Animator animator) {
cancelAnimations();
- mRunningDragAnimator = animator;
+ mRunningAnimator = animator;
animator.start();
}
+ /**
+ * Animate the alpha of the expanded view between visible (1) and invisible (0).
+ * {@link BubbleBarExpandedView} requires
+ * {@link com.android.wm.shell.bubbles.BubbleExpandedView#setSurfaceZOrderedOnTop(boolean)} to
+ * be called before alpha can be applied.
+ * Only supports alpha of 1 or 0. Otherwise we can't reset surface z-order at the end.
+ */
+ private ObjectAnimator createAlphaAnimator(BubbleBarExpandedView bubbleBarExpandedView,
+ boolean visible) {
+ ObjectAnimator animator = ObjectAnimator.ofFloat(bubbleBarExpandedView, TASK_VIEW_ALPHA,
+ visible ? 1f : 0f);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ // Move task view to the top of the window so alpha can be applied to it
+ bubbleBarExpandedView.setSurfaceZOrderedOnTop(true);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ bubbleBarExpandedView.setContentVisibility(visible);
+ if (!visible) {
+ // Hide the expanded view before we reset the z-ordering
+ bubbleBarExpandedView.setVisibility(INVISIBLE);
+ }
+ bubbleBarExpandedView.setSurfaceZOrderedOnTop(false);
+ }
+ });
+ return animator;
+ }
+
private static void setDragPivot(BubbleBarExpandedView bbev) {
bbev.setPivotX(bbev.getWidth() / 2f);
bbev.setPivotY(0f);
}
- private class DragAnimatorListenerAdapter extends AnimatorListenerAdapter {
+ private static void setPivotToCenter(BubbleBarExpandedView bbev) {
+ bbev.setPivotX(bbev.getWidth() / 2f);
+ bbev.setPivotY(bbev.getHeight() / 2f);
+ }
+
+ private static class DragAnimatorListenerAdapter extends AnimatorListenerAdapter {
private final BubbleBarExpandedView mBubbleBarExpandedView;
@@ -523,11 +690,9 @@
public void onAnimationStart(Animator animation) {
mBubbleBarExpandedView.setAnimating(true);
}
-
@Override
public void onAnimationEnd(Animator animation) {
mBubbleBarExpandedView.setAnimating(false);
- mRunningDragAnimator = null;
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index ed49417..2c0483c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -85,6 +85,22 @@
}
};
+ /**
+ * Property to set alpha for the task view
+ */
+ public static final FloatProperty<BubbleBarExpandedView> TASK_VIEW_ALPHA = new FloatProperty<>(
+ "taskViewAlpha") {
+ @Override
+ public void setValue(BubbleBarExpandedView bbev, float alpha) {
+ bbev.setTaskViewAlpha(alpha);
+ }
+
+ @Override
+ public Float get(BubbleBarExpandedView bbev) {
+ return bbev.mTaskView != null ? bbev.mTaskView.getAlpha() : bbev.getAlpha();
+ }
+ };
+
private static final String TAG = BubbleBarExpandedView.class.getSimpleName();
private static final int INVALID_TASK_ID = -1;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 0c05e3c..425afbe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -103,8 +103,7 @@
mPositioner = mBubbleController.getPositioner();
mBubbleLogger = bubbleLogger;
- mAnimationHelper = new BubbleBarAnimationHelper(context,
- this, mPositioner);
+ mAnimationHelper = new BubbleBarAnimationHelper(context, mPositioner);
mEducationViewController = new BubbleEducationViewController(context, (boolean visible) -> {
if (mExpandedView == null) return;
mExpandedView.setObscured(visible);
@@ -183,8 +182,14 @@
// Already showing this bubble, skip animating
return;
}
+ BubbleViewProvider previousBubble = null;
if (mExpandedBubble != null && !b.getKey().equals(mExpandedBubble.getKey())) {
- removeView(mExpandedView);
+ if (mIsExpanded) {
+ // Previous expanded view open, keep it visible to animate the switch
+ previousBubble = mExpandedBubble;
+ } else {
+ removeView(mExpandedView);
+ }
mExpandedView = null;
}
if (mExpandedView == null) {
@@ -246,7 +251,8 @@
mIsExpanded = true;
mBubbleController.getSysuiProxy().onStackExpandChanged(true);
- mAnimationHelper.animateExpansion(mExpandedBubble, () -> {
+
+ final Runnable afterAnimation = () -> {
if (mExpandedView == null) return;
// Touch delegate for the menu
BubbleBarHandleView view = mExpandedView.getHandleView();
@@ -256,7 +262,18 @@
mHandleTouchDelegate = new TouchDelegate(mHandleTouchBounds,
mExpandedView.getHandleView());
setTouchDelegate(mHandleTouchDelegate);
- });
+ };
+
+ if (previousBubble != null) {
+ final BubbleBarExpandedView previousExpandedView =
+ previousBubble.getBubbleBarExpandedView();
+ mAnimationHelper.animateSwitch(previousBubble, mExpandedBubble, () -> {
+ removeView(previousExpandedView);
+ afterAnimation.run();
+ });
+ } else {
+ mAnimationHelper.animateExpansion(mExpandedBubble, afterAnimation);
+ }
showScrim(true);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
index 62d5098..bc56637 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
@@ -30,7 +30,7 @@
* desktop windowing environment.
*/
fun isTopActivityExemptFromDesktopWindowing(context: Context, task: TaskInfo) =
- (isSystemUiTask(context, task) || (task.isTopActivityTransparent && task.numActivities == 1))
+ (isSystemUiTask(context, task) || (task.numActivities > 0 && task.isActivityStackTransparent))
&& !task.isTopActivityNoDisplay
private fun isSystemUiTask(context: Context, task: TaskInfo): Boolean {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxConfiguration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxConfiguration.kt
index 83a8e31..244ff99 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxConfiguration.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxConfiguration.kt
@@ -36,6 +36,21 @@
// Color resource id for the solid color letterbox background type.
private var letterboxBackgroundColorResourceIdOverride: Int? = null
+ // Default value for corners radius for activities presented in the letterbox mode.
+ // Values < 0 will be ignored.
+ private val letterboxActivityDefaultCornersRadius: Int
+
+ // Current corners radius for activities presented in the letterbox mode.
+ // Values can be modified at runtime and values < 0 will be ignored.
+ private var letterboxActivityCornersRadius = 0
+
+ init {
+ letterboxActivityDefaultCornersRadius = context.resources.getInteger(
+ R.integer.config_letterboxActivityCornersRadius
+ )
+ letterboxActivityCornersRadius = letterboxActivityDefaultCornersRadius
+ }
+
/**
* Sets color of letterbox background which is used when using the solid background mode.
*/
@@ -64,7 +79,7 @@
}
// Query color dynamically because material colors extracted from wallpaper are updated
// when wallpaper is changed.
- return Color.valueOf(context.getResources().getColor(colorId!!, /* theme */null))
+ return Color.valueOf(context.getResources().getColor(colorId!!, null))
}
/**
@@ -79,4 +94,33 @@
* The background color for the Letterbox.
*/
fun getBackgroundColorRgbArray(): FloatArray = getLetterboxBackgroundColor().components
+
+ /**
+ * Overrides corners radius for activities presented in the letterbox mode. Values < 0,
+ * will be ignored and corners of the activity won't be rounded.
+ */
+ fun setLetterboxActivityCornersRadius(cornersRadius: Int) {
+ letterboxActivityCornersRadius = cornersRadius
+ }
+
+ /**
+ * Resets corners radius for activities presented in the letterbox mode.
+ */
+ fun resetLetterboxActivityCornersRadius() {
+ letterboxActivityCornersRadius = letterboxActivityDefaultCornersRadius
+ }
+
+ /**
+ * Whether corners of letterboxed activities are rounded.
+ */
+ fun isLetterboxActivityCornersRounded(): Boolean {
+ return getLetterboxActivityCornersRadius() > 0
+ }
+
+ /**
+ * Gets corners radius for activities presented in the letterbox mode.
+ */
+ fun getLetterboxActivityCornersRadius(): Int {
+ return maxOf(letterboxActivityCornersRadius, 0)
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategy.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategy.kt
index 9e3edf6..0c3769e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategy.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategy.kt
@@ -16,6 +16,8 @@
package com.android.wm.shell.compatui.letterbox
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.MULTIPLE_SURFACES
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.SINGLE_SURFACE
import com.android.wm.shell.dagger.WMSingleton
import javax.inject.Inject
@@ -24,24 +26,28 @@
* implementing letterbox in shell.
*/
@WMSingleton
-class LetterboxControllerStrategy @Inject constructor() {
+class LetterboxControllerStrategy @Inject constructor(
+ private val letterboxConfiguration: LetterboxConfiguration
+) {
// Different letterbox implementation modes.
enum class LetterboxMode { SINGLE_SURFACE, MULTIPLE_SURFACES }
@Volatile
- private var currentMode: LetterboxMode = LetterboxMode.SINGLE_SURFACE
+ private var currentMode: LetterboxMode = SINGLE_SURFACE
fun configureLetterboxMode() {
// TODO(b/377875146): Define criteria for switching between [LetterboxMode]s.
- currentMode = if (android.os.SystemProperties.getInt(
- "multi_interface",
- 0
- ) == 0
- ) {
- LetterboxMode.SINGLE_SURFACE
+ // At the moment, we use the presence of rounded corners to understand if to use a single
+ // surface or multiple surfaces for the letterbox areas. This rule will change when
+ // considering transparent activities which won't have rounded corners leading to the
+ // [MULTIPLE_SURFACES] option.
+ // The chosen strategy will depend on performance considerations,
+ // including surface memory usage and the impact of the rounded corners solution.
+ currentMode = if (letterboxConfiguration.isLetterboxActivityCornersRounded()) {
+ SINGLE_SURFACE
} else {
- LetterboxMode.MULTIPLE_SURFACES
+ MULTIPLE_SURFACES
}
}
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 f46b955..86e0d08 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
@@ -152,6 +152,7 @@
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer;
import com.android.wm.shell.windowdecor.common.viewhost.DefaultWindowDecorViewHostSupplier;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationPromoController;
import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController;
@@ -301,6 +302,7 @@
Transitions transitions,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
FocusTransitionObserver focusTransitionObserver,
+ WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
Optional<DesktopModeWindowDecorViewModel> desktopModeWindowDecorViewModel) {
if (desktopModeWindowDecorViewModel.isPresent()) {
return desktopModeWindowDecorViewModel.get();
@@ -318,7 +320,8 @@
rootTaskDisplayAreaOrganizer,
syncQueue,
transitions,
- focusTransitionObserver);
+ focusTransitionObserver,
+ windowDecorViewHostSupplier);
}
@WMSingleton
@@ -343,7 +346,7 @@
@WMSingleton
@Provides
- static WindowDecorViewHostSupplier provideWindowDecorViewHostSupplier(
+ static WindowDecorViewHostSupplier<WindowDecorViewHost> provideWindowDecorViewHostSupplier(
@ShellMainThread @NonNull CoroutineScope mainScope) {
return new DefaultWindowDecorViewHostSupplier(mainScope);
}
@@ -908,6 +911,7 @@
InteractionJankMonitor interactionJankMonitor,
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
+ WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
MultiInstanceHelper multiInstanceHelper,
Optional<DesktopTasksLimiter> desktopTasksLimiter,
AppHandleEducationController appHandleEducationController,
@@ -927,8 +931,8 @@
displayInsetsController, syncQueue, transitions, desktopTasksController,
desktopImmersiveController.get(),
rootTaskDisplayAreaOrganizer, interactionJankMonitor, genericLinksParser,
- assistContentRequester, multiInstanceHelper, desktopTasksLimiter,
- appHandleEducationController, appToWebEducationController,
+ assistContentRequester, windowDecorViewHostSupplier, multiInstanceHelper,
+ desktopTasksLimiter, appHandleEducationController, appToWebEducationController,
windowDecorCaptionHandleRepository, activityOrientationChangeHandler,
focusTransitionObserver, desktopModeEventLogger, desktopModeUiEventLogger));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index e08f9b2..d5a2a40 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -1510,28 +1510,20 @@
/** Open an existing instance of an app. */
fun openInstance(callingTask: RunningTaskInfo, requestedTaskId: Int) {
- val wct = WindowContainerTransaction()
- val options = createNewWindowOptions(callingTask)
- if (options.launchWindowingMode == WINDOWING_MODE_FREEFORM) {
- wct.startTask(requestedTaskId, options.toBundle())
- val taskIdToMinimize =
- bringDesktopAppsToFrontBeforeShowingNewTask(
- callingTask.displayId,
- wct,
+ if (callingTask.isFreeform) {
+ val requestedTaskInfo = shellTaskOrganizer.getRunningTaskInfo(requestedTaskId)
+ if (requestedTaskInfo?.isFreeform == true) {
+ // If requested task is an already open freeform task, just move it to front.
+ moveTaskToFront(requestedTaskId)
+ } else {
+ moveBackgroundTaskToDesktop(
requestedTaskId,
+ WindowContainerTransaction(),
+ DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON,
)
- val exitResult =
- desktopImmersiveController.exitImmersiveIfApplicable(
- wct = wct,
- displayId = callingTask.displayId,
- excludeTaskId = requestedTaskId,
- reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
- )
- val transition = transitions.startTransition(TRANSIT_OPEN, wct, null)
- taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
- addPendingAppLaunchTransition(transition, requestedTaskId, taskIdToMinimize)
- exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
+ }
} else {
+ val options = createNewWindowOptions(callingTask)
val splitPosition = splitScreenController.determineNewInstancePosition(callingTask)
splitScreenController.startTask(
requestedTaskId,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 11110f1..d23c9d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -19,7 +19,6 @@
import android.content.Intent.FILL_IN_COMPONENT
import android.graphics.PointF
import android.graphics.Rect
-import android.os.Bundle
import android.os.IBinder
import android.os.SystemClock
import android.os.SystemProperties
@@ -139,7 +138,11 @@
taskUser,
)
val wct = WindowContainerTransaction()
- wct.sendPendingIntent(pendingIntent, launchHomeIntent, Bundle())
+ // The app that is being dragged into desktop mode might cause new transitions, make this
+ // launch transient to make sure those transitions can execute in parallel and thus won't
+ // block the end-drag transition.
+ val intentOptions = ActivityOptions.makeBasic().setTransientLaunch()
+ wct.sendPendingIntent(pendingIntent, launchHomeIntent, intentOptions.toBundle())
val startTransitionToken =
transitions.startTransition(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, wct, this)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 885f3db..0b91966 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -68,6 +68,8 @@
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.FocusTransitionObserver;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
/**
@@ -90,6 +92,7 @@
private final Transitions mTransitions;
private final Region mExclusionRegion = Region.obtain();
private final InputManager mInputManager;
+ private final WindowDecorViewHostSupplier<WindowDecorViewHost> mWindowDecorViewHostSupplier;
private TaskOperations mTaskOperations;
private FocusTransitionObserver mFocusTransitionObserver;
@@ -130,7 +133,8 @@
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
SyncTransactionQueue syncQueue,
Transitions transitions,
- FocusTransitionObserver focusTransitionObserver) {
+ FocusTransitionObserver focusTransitionObserver,
+ WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier) {
mContext = context;
mMainExecutor = shellExecutor;
mMainHandler = mainHandler;
@@ -143,6 +147,7 @@
mSyncQueue = syncQueue;
mTransitions = transitions;
mFocusTransitionObserver = focusTransitionObserver;
+ mWindowDecorViewHostSupplier = windowDecorViewHostSupplier;
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
mTaskOperations = new TaskOperations(null, mContext, mSyncQueue);
}
@@ -332,7 +337,8 @@
mMainHandler,
mBgExecutor,
mMainChoreographer,
- mSyncQueue);
+ mSyncQueue,
+ mWindowDecorViewHostSupplier);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
final FluidResizeTaskPositioner taskPositioner =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 112e429..23bb2aa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -58,6 +58,8 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
/**
@@ -90,9 +92,10 @@
Handler handler,
@ShellBackgroundThread ShellExecutor bgExecutor,
Choreographer choreographer,
- SyncTransactionQueue syncQueue) {
- super(context, userContext, displayController, taskOrganizer, handler, taskInfo,
- taskSurface);
+ SyncTransactionQueue syncQueue,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier) {
+ super(context, userContext, displayController, taskOrganizer, taskInfo,
+ taskSurface, windowDecorViewHostSupplier);
mHandler = handler;
mBgExecutor = bgExecutor;
mChoreographer = choreographer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index e8b02dc..9dbac76 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -79,6 +79,7 @@
import android.view.SurfaceControl.Transaction;
import android.view.View;
import android.view.ViewConfiguration;
+import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.window.DesktopModeFlags;
import android.window.TaskSnapshot;
@@ -139,6 +140,8 @@
import com.android.wm.shell.transition.FocusTransitionObserver;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import com.android.wm.shell.windowdecor.extension.InsetsStateKt;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
@@ -217,6 +220,7 @@
private boolean mInImmersiveMode;
private final String mSysUIPackageName;
private final AssistContentRequester mAssistContentRequester;
+ private final WindowDecorViewHostSupplier<WindowDecorViewHost> mWindowDecorViewHostSupplier;
private final DisplayChangeController.OnDisplayChangingListener mOnDisplayChangingListener;
private final ISystemGestureExclusionListener mGestureExclusionListener =
@@ -260,6 +264,7 @@
InteractionJankMonitor interactionJankMonitor,
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
MultiInstanceHelper multiInstanceHelper,
Optional<DesktopTasksLimiter> desktopTasksLimiter,
AppHandleEducationController appHandleEducationController,
@@ -289,6 +294,7 @@
desktopImmersiveController,
genericLinksParser,
assistContentRequester,
+ windowDecorViewHostSupplier,
multiInstanceHelper,
new DesktopModeWindowDecoration.Factory(),
new InputMonitorFactory(),
@@ -329,6 +335,7 @@
DesktopImmersiveController desktopImmersiveController,
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
MultiInstanceHelper multiInstanceHelper,
DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
InputMonitorFactory inputMonitorFactory,
@@ -381,6 +388,7 @@
mWindowDecorCaptionHandleRepository = windowDecorCaptionHandleRepository;
mActivityOrientationChangeHandler = activityOrientationChangeHandler;
mAssistContentRequester = assistContentRequester;
+ mWindowDecorViewHostSupplier = windowDecorViewHostSupplier;
mOnDisplayChangingListener = (displayId, fromRotation, toRotation, displayAreaInfo, t) -> {
DesktopModeWindowDecoration decoration;
RunningTaskInfo taskInfo;
@@ -965,8 +973,11 @@
}
if (mInputManager != null
&& !Flags.enableAccessibleCustomHeaders()) {
- // Pilfer so that windows below receive cancellations for this gesture.
- mInputManager.pilferPointers(v.getViewRootImpl().getInputToken());
+ ViewRootImpl viewRootImpl = v.getViewRootImpl();
+ if (viewRootImpl != null) {
+ // Pilfer so that windows below receive cancellations for this gesture.
+ mInputManager.pilferPointers(viewRootImpl.getInputToken());
+ }
}
if (isUpOrCancel) {
// Gesture is finished, reset state.
@@ -1623,6 +1634,7 @@
mRootTaskDisplayAreaOrganizer,
mGenericLinksParser,
mAssistContentRequester,
+ mWindowDecorViewHostSupplier,
mMultiInstanceHelper,
mWindowDecorCaptionHandleRepository,
mDesktopModeEventLogger);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 3d0c8a7..6562f38 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -105,6 +105,8 @@
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.shared.multiinstance.ManageWindowsViewContainer;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
@@ -127,7 +129,6 @@
*/
public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
private static final String TAG = "DesktopModeWindowDecoration";
- private static final int CAPTURED_LINK_TIMEOUT_MS = 7000;
@VisibleForTesting
static final long CLOSE_MAXIMIZE_MENU_DELAY_MS = 150L;
@@ -199,7 +200,6 @@
// being hovered. There's a small delay after stopping the hover, to allow a quick reentry
// to cancel the close.
private final Runnable mCloseMaximizeWindowRunnable = this::closeMaximizeMenu;
- private final Runnable mCapturedLinkExpiredRunnable = this::onCapturedLinkExpired;
private final MultiInstanceHelper mMultiInstanceHelper;
private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository;
private final DesktopUserRepositories mDesktopUserRepositories;
@@ -221,6 +221,7 @@
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
MultiInstanceHelper multiInstanceHelper,
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
DesktopModeEventLogger desktopModeEventLogger) {
@@ -232,6 +233,7 @@
WindowContainerTransaction::new, SurfaceControl::new, new WindowManagerWrapper(
context.getSystemService(WindowManager.class)),
new SurfaceControlViewHostFactory() {},
+ windowDecorViewHostSupplier,
DefaultMaximizeMenuFactory.INSTANCE,
DefaultHandleMenuFactory.INSTANCE, multiInstanceHelper,
windowDecorCaptionHandleRepository, desktopModeEventLogger);
@@ -260,15 +262,16 @@
Supplier<SurfaceControl> surfaceControlSupplier,
WindowManagerWrapper windowManagerWrapper,
SurfaceControlViewHostFactory surfaceControlViewHostFactory,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
MaximizeMenuFactory maximizeMenuFactory,
HandleMenuFactory handleMenuFactory,
MultiInstanceHelper multiInstanceHelper,
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
DesktopModeEventLogger desktopModeEventLogger) {
- super(context, userContext, displayController, taskOrganizer, handler, taskInfo,
+ super(context, userContext, displayController, taskOrganizer, taskInfo,
taskSurface, surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
windowContainerTransactionSupplier, surfaceControlSupplier,
- surfaceControlViewHostFactory, desktopModeEventLogger);
+ surfaceControlViewHostFactory, windowDecorViewHostSupplier, desktopModeEventLogger);
mSplitScreenController = splitScreenController;
mHandler = handler;
mBgExecutor = bgExecutor;
@@ -548,22 +551,12 @@
return;
}
mCapturedLink = new CapturedLink(capturedLink, timeStamp);
- mHandler.postDelayed(mCapturedLinkExpiredRunnable, CAPTURED_LINK_TIMEOUT_MS);
- }
-
- private void onCapturedLinkExpired() {
- mHandler.removeCallbacks(mCapturedLinkExpiredRunnable);
- if (mCapturedLink != null) {
- mCapturedLink.setExpired();
- }
}
@Nullable
private Intent getBrowserLink() {
final Uri browserLink;
- // If the captured link is available and has not expired, return the captured link.
- // Otherwise, return the generic link which is set to null if a generic link is unavailable.
- if (mCapturedLink != null && !mCapturedLink.mExpired) {
+ if (isCapturedLinkAvailable()) {
browserLink = mCapturedLink.mUri;
} else if (mWebUri != null) {
browserLink = mWebUri;
@@ -675,7 +668,13 @@
}
private boolean isCapturedLinkAvailable() {
- return mCapturedLink != null && !mCapturedLink.mExpired;
+ return mCapturedLink != null && !mCapturedLink.mUsed;
+ }
+
+ private void onCapturedLinkUsed() {
+ if (mCapturedLink != null) {
+ mCapturedLink.setUsed();
+ }
}
private void notifyNoCaptionHandle() {
@@ -1365,7 +1364,7 @@
/* onAspectRatioSettingsClickListener= */ mOnChangeAspectRatioClickListener,
/* openInBrowserClickListener= */ (intent) -> {
mOpenInBrowserClickListener.accept(intent);
- onCapturedLinkExpired();
+ onCapturedLinkUsed();
if (Flags.enableDesktopWindowingAppToWebEducationIntegration()) {
mWindowDecorCaptionHandleRepository.onAppToWebUsage();
}
@@ -1766,6 +1765,8 @@
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost>
+ windowDecorViewHostSupplier,
MultiInstanceHelper multiInstanceHelper,
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
DesktopModeEventLogger desktopModeEventLogger) {
@@ -1786,6 +1787,7 @@
rootTaskDisplayAreaOrganizer,
genericLinksParser,
assistContentRequester,
+ windowDecorViewHostSupplier,
multiInstanceHelper,
windowDecorCaptionHandleRepository,
desktopModeEventLogger);
@@ -1796,16 +1798,15 @@
static class CapturedLink {
private final long mTimeStamp;
private final Uri mUri;
- private boolean mExpired;
+ private boolean mUsed;
CapturedLink(@NonNull Uri uri, long timeStamp) {
mUri = uri;
mTimeStamp = timeStamp;
- mExpired = false;
}
- void setExpired() {
- mExpired = true;
+ private void setUsed() {
+ mUsed = true;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 261d400..5d1bedb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -39,7 +39,6 @@
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Binder;
-import android.os.Handler;
import android.os.Trace;
import android.view.Display;
import android.view.InsetsSource;
@@ -59,10 +58,11 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
-import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement;
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import com.android.wm.shell.windowdecor.extension.InsetsStateKt;
import java.util.ArrayList;
@@ -90,10 +90,10 @@
implements AutoCloseable {
/**
- * The Z-order of {@link #mCaptionContainerSurface}.
+ * The Z-order of the caption surface.
* <p>
* We use {@link #mDecorationContainerSurface} to define input window for task resizing; by
- * layering it in front of {@link #mCaptionContainerSurface}, we can allow it to handle input
+ * layering it in front of the caption surface, we can allow it to handle input
* prior to caption view itself, treating corner inputs as resize events rather than
* repositioning.
*/
@@ -102,7 +102,7 @@
* The Z-order of the task input sink in {@link DragPositioningCallback}.
* <p>
* This task input sink is used to prevent undesired dispatching of motion events out of task
- * bounds; by layering it behind {@link #mCaptionContainerSurface}, we allow captions to handle
+ * bounds; by layering it behind the caption surface, we allow captions to handle
* input events first.
*/
static final int INPUT_SINK_Z_ORDER = -2;
@@ -123,12 +123,13 @@
final @NonNull Context mUserContext;
final @NonNull DisplayController mDisplayController;
final @NonNull DesktopModeEventLogger mDesktopModeEventLogger;
- private final @ShellMainThread Handler mMainHandler;
final ShellTaskOrganizer mTaskOrganizer;
final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier;
final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
final Supplier<WindowContainerTransaction> mWindowContainerTransactionSupplier;
final SurfaceControlViewHostFactory mSurfaceControlViewHostFactory;
+ @NonNull private final WindowDecorViewHostSupplier<WindowDecorViewHost>
+ mWindowDecorViewHostSupplier;
private final DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener =
new DisplayController.OnDisplaysChangedListener() {
@Override
@@ -153,9 +154,7 @@
Display mDisplay;
SurfaceControl mDecorationContainerSurface;
- SurfaceControl mCaptionContainerSurface;
- private CaptionWindowlessWindowManager mCaptionWindowManager;
- private SurfaceControlViewHost mViewHost;
+ private WindowDecorViewHost mViewHost;
private Configuration mWindowDecorConfig;
TaskDragResizer mTaskDragResizer;
boolean mIsCaptionVisible;
@@ -170,20 +169,19 @@
private final Binder mOwner = new Binder();
private final float[] mTmpColor = new float[3];
- private Runnable mCurrentUpdateViewHostRunnable;
-
WindowDecoration(
Context context,
@NonNull Context userContext,
DisplayController displayController,
ShellTaskOrganizer taskOrganizer,
- @ShellMainThread Handler mainHandler,
RunningTaskInfo taskInfo,
- SurfaceControl taskSurface) {
- this(context, userContext, displayController, taskOrganizer, mainHandler, taskInfo,
+ SurfaceControl taskSurface,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier) {
+ this(context, userContext, displayController, taskOrganizer, taskInfo,
taskSurface, SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
WindowContainerTransaction::new, SurfaceControl::new,
- new SurfaceControlViewHostFactory() {}, new DesktopModeEventLogger());
+ new SurfaceControlViewHostFactory() {}, windowDecorViewHostSupplier,
+ new DesktopModeEventLogger());
}
WindowDecoration(
@@ -191,7 +189,6 @@
@NonNull Context userContext,
@NonNull DisplayController displayController,
ShellTaskOrganizer taskOrganizer,
- @ShellMainThread Handler mainHandler,
RunningTaskInfo taskInfo,
@NonNull SurfaceControl taskSurface,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
@@ -199,13 +196,13 @@
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
Supplier<SurfaceControl> surfaceControlSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
@NonNull DesktopModeEventLogger desktopModeEventLogger
) {
mContext = context;
mUserContext = userContext;
mDisplayController = displayController;
mTaskOrganizer = taskOrganizer;
- mMainHandler = mainHandler;
mTaskInfo = taskInfo;
mTaskSurface = cloneSurfaceControl(taskSurface, surfaceControlSupplier);
mDesktopModeEventLogger = desktopModeEventLogger;
@@ -213,6 +210,7 @@
mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
mWindowContainerTransactionSupplier = windowContainerTransactionSupplier;
mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
+ mWindowDecorViewHostSupplier = windowDecorViewHostSupplier;
mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
final InsetsState insetsState = mDisplayController.getInsetsState(mTaskInfo.displayId);
mIsStatusBarVisible = insetsState != null
@@ -292,43 +290,20 @@
outResult.mCaptionY = 0;
outResult.mCaptionTopPadding = params.mCaptionTopPadding;
+ Trace.beginSection("relayout-createViewHostIfNeeded");
+ createViewHostIfNeeded(mDecorWindowContext, mDisplay);
+ Trace.endSection();
+
Trace.beginSection("WindowDecoration#relayout-updateSurfacesAndInsets");
+ final SurfaceControl captionSurface = mViewHost.getSurfaceControl();
updateDecorationContainerSurface(startT, outResult);
- final SurfaceControl captionSurface = getOrCreateCaptionSurface();
updateCaptionContainerSurface(captionSurface, startT, outResult);
updateCaptionInsets(params, wct, outResult, taskBounds);
updateTaskSurface(params, startT, finishT, outResult);
Trace.endSection();
Trace.beginSection("WindowDecoration#relayout-updateViewHost");
- clearCurrentViewHostRunnable();
- if (params.mAsyncViewHost) {
- mCurrentUpdateViewHostRunnable = () -> updateViewHost(params, startT, outResult);
- mMainHandler.post(mCurrentUpdateViewHostRunnable);
- } else {
- updateViewHost(params, startT, outResult);
- }
- Trace.endSection();
-
- Trace.endSection(); // WindowDecoration#relayout
- }
-
- private void clearCurrentViewHostRunnable() {
- if (mCurrentUpdateViewHostRunnable != null) {
- mMainHandler.removeCallbacks(mCurrentUpdateViewHostRunnable);
- mCurrentUpdateViewHostRunnable = null;
- }
- }
-
- private void updateViewHost(RelayoutParams params, SurfaceControl.Transaction startT,
- RelayoutResult<T> outResult) {
- createCaptionWindowManagerIfNeeded();
- Trace.beginSection("updateViewHost-createViewHostIfNeeded");
- final boolean firstLayout = createViewHostIfNeeded(mDecorWindowContext, mDisplay);
- Trace.endSection();
-
outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0);
- mCaptionWindowManager.setConfiguration(mTaskInfo.getConfiguration());
final Rect localCaptionBounds = new Rect(
outResult.mCaptionX,
outResult.mCaptionY,
@@ -337,50 +312,21 @@
final Region touchableRegion = params.mLimitTouchRegionToSystemAreas
? calculateLimitedTouchableRegion(params, localCaptionBounds)
: null;
- if (params.mLimitTouchRegionToSystemAreas) {
- mCaptionWindowManager.setTouchRegion(mViewHost, touchableRegion);
- }
- if (touchableRegion != null) {
- touchableRegion.recycle();
- }
- updateViewHierarchy(params, outResult, startT, firstLayout);
+ updateViewHierarchy(params, outResult, startT, touchableRegion);
+ Trace.endSection();
+
+ Trace.endSection(); // WindowDecoration#relayout
}
- private SurfaceControl getOrCreateCaptionSurface() {
- if (mCaptionContainerSurface == null) {
- final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
- mCaptionContainerSurface = builder
- .setName("Caption container of Task=" + mTaskInfo.taskId)
- .setContainerLayer()
- .setParent(mDecorationContainerSurface)
- .setCallsite("WindowDecoration.updateCaptionContainerSurface")
- .build();
- }
- return mCaptionContainerSurface;
- }
-
- private boolean createViewHostIfNeeded(@NonNull Context context, @NonNull Display display) {
+ private void createViewHostIfNeeded(@NonNull Context context, @NonNull Display display) {
if (mViewHost == null) {
- mViewHost = mSurfaceControlViewHostFactory.create(context, display,
- mCaptionWindowManager);
- Trace.endSection();
- return true;
- }
- return false;
- }
-
- private void createCaptionWindowManagerIfNeeded() {
- if (mCaptionWindowManager == null) {
- // Put caption under a container surface because ViewRootImpl sets the destination frame
- // of windowless window layers and BLASTBufferQueue#update() doesn't support offset.
- mCaptionWindowManager = new CaptionWindowlessWindowManager(
- mTaskInfo.getConfiguration(), mCaptionContainerSurface);
+ mViewHost = mWindowDecorViewHostSupplier.acquire(context, display);
}
}
private void updateViewHierarchy(@NonNull RelayoutParams params,
@NonNull RelayoutResult<T> outResult, @NonNull SurfaceControl.Transaction startT,
- boolean firstLayout) {
+ @Nullable Region touchableRegion) {
Trace.beginSection("WindowDecoration#updateViewHierarchy");
final WindowManager.LayoutParams lp =
new WindowManager.LayoutParams(
@@ -392,16 +338,15 @@
lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
lp.setTrustedOverlay();
lp.inputFeatures = params.mInputFeatures;
- if (params.mApplyStartTransactionOnDraw) {
- if (params.mAsyncViewHost) {
+ if (params.mAsyncViewHost) {
+ if (params.mApplyStartTransactionOnDraw) {
throw new IllegalArgumentException("Cannot use sync draw tx with async relayout");
}
- mViewHost.getRootSurfaceControl().applyTransactionOnDraw(startT);
- }
- if (firstLayout) {
- mViewHost.setView(outResult.mRootView, lp);
+ mViewHost.updateViewAsync(outResult.mRootView, lp, mTaskInfo.configuration,
+ touchableRegion);
} else {
- mViewHost.relayout(lp);
+ mViewHost.updateView(outResult.mRootView, lp, mTaskInfo.configuration,
+ touchableRegion, params.mApplyStartTransactionOnDraw ? startT : null);
}
Trace.endSection();
}
@@ -719,18 +664,11 @@
}
void releaseViews(WindowContainerTransaction wct) {
- if (mViewHost != null) {
- mViewHost.release();
- mViewHost = null;
- }
-
- mCaptionWindowManager = null;
-
final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
boolean released = false;
- if (mCaptionContainerSurface != null) {
- t.remove(mCaptionContainerSurface);
- mCaptionContainerSurface = null;
+ if (mViewHost != null) {
+ mWindowDecorViewHostSupplier.release(mViewHost, t);
+ mViewHost = null;
released = true;
}
@@ -753,7 +691,6 @@
@Override
public void close() {
Trace.beginSection("WindowDecoration#close");
- clearCurrentViewHostRunnable();
mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
final WindowContainerTransaction wct = mWindowContainerTransactionSupplier.get();
releaseViews(wct);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt
index c470eef..50aa21e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt
@@ -17,6 +17,7 @@
import android.content.Context
import android.content.res.Configuration
+import android.graphics.Region
import android.view.Display
import android.view.SurfaceControl
import android.view.SurfaceControlViewHost
@@ -59,7 +60,7 @@
.setCallsite("DefaultWindowDecorViewHost#init")
.build()
- private var wwm: WindowlessWindowManager? = null
+ private var wwm: WindowDecorWindowlessWindowManager? = null
@VisibleForTesting var viewHost: SurfaceControlViewHost? = null
private var currentUpdateJob: Job? = null
@@ -70,11 +71,12 @@
view: View,
attrs: WindowManager.LayoutParams,
configuration: Configuration,
+ touchableRegion: Region?,
onDrawTransaction: SurfaceControl.Transaction?,
) {
Trace.beginSection("DefaultWindowDecorViewHost#updateView")
clearCurrentUpdateJob()
- updateViewHost(view, attrs, configuration, onDrawTransaction)
+ updateViewHost(view, attrs, configuration, touchableRegion, onDrawTransaction)
Trace.endSection()
}
@@ -82,12 +84,19 @@
view: View,
attrs: WindowManager.LayoutParams,
configuration: Configuration,
+ touchableRegion: Region?,
) {
Trace.beginSection("DefaultWindowDecorViewHost#updateViewAsync")
clearCurrentUpdateJob()
currentUpdateJob =
mainScope.launch {
- updateViewHost(view, attrs, configuration, onDrawTransaction = null)
+ updateViewHost(
+ view,
+ attrs,
+ configuration,
+ touchableRegion,
+ onDrawTransaction = null
+ )
}
Trace.endSection()
}
@@ -102,13 +111,13 @@
view: View,
attrs: WindowManager.LayoutParams,
configuration: Configuration,
+ touchableRegion: Region?,
onDrawTransaction: SurfaceControl.Transaction?,
) {
Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost")
if (wwm == null) {
- wwm = WindowlessWindowManager(configuration, rootSurface, null)
+ wwm = WindowDecorWindowlessWindowManager(configuration, rootSurface)
}
- requireWindowlessWindowManager().setConfiguration(configuration)
if (viewHost == null) {
viewHost =
surfaceControlViewHostFactory.invoke(
@@ -118,6 +127,10 @@
"DefaultWindowDecorViewHost#updateViewHost",
)
}
+ requireWindowlessWindowManager().apply {
+ setConfiguration(configuration)
+ setTouchRegion(requireViewHost(), touchableRegion)
+ }
onDrawTransaction?.let { requireViewHost().rootSurfaceControl.applyTransactionOnDraw(it) }
if (requireViewHost().view == null) {
Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost-setView")
@@ -137,7 +150,7 @@
currentUpdateJob = null
}
- private fun requireWindowlessWindowManager(): WindowlessWindowManager {
+ private fun requireWindowlessWindowManager(): WindowDecorWindowlessWindowManager {
return wwm ?: error("Expected non-null windowless window manager")
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostSupplier.kt
index 27ffd6c..7821619 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostSupplier.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostSupplier.kt
@@ -24,14 +24,15 @@
/**
* A supplier of [DefaultWindowDecorViewHost]s. It creates a new one every time one is requested.
*/
-class DefaultWindowDecorViewHostSupplier(@ShellMainThread private val mainScope: CoroutineScope) :
- WindowDecorViewHostSupplier<DefaultWindowDecorViewHost> {
+class DefaultWindowDecorViewHostSupplier(
+ @ShellMainThread private val mainScope: CoroutineScope
+) : WindowDecorViewHostSupplier<WindowDecorViewHost> {
- override fun acquire(context: Context, display: Display): DefaultWindowDecorViewHost {
+ override fun acquire(context: Context, display: Display): WindowDecorViewHost {
return DefaultWindowDecorViewHost(context, mainScope, display)
}
- override fun release(viewHost: DefaultWindowDecorViewHost, t: SurfaceControl.Transaction) {
+ override fun release(viewHost: WindowDecorViewHost, t: SurfaceControl.Transaction) {
viewHost.release(t)
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHost.kt
index 7c1479e..2dcbbac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHost.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHost.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.windowdecor.common.viewhost
import android.content.res.Configuration
+import android.graphics.Region
import android.view.SurfaceControl
import android.view.View
import android.view.WindowManager
@@ -34,11 +35,17 @@
view: View,
attrs: WindowManager.LayoutParams,
configuration: Configuration,
- onDrawTransaction: SurfaceControl.Transaction?,
+ touchableRegion: Region? = null,
+ onDrawTransaction: SurfaceControl.Transaction? = null,
)
/** Asynchronously update the view hierarchy of this view host. */
- fun updateViewAsync(view: View, attrs: WindowManager.LayoutParams, configuration: Configuration)
+ fun updateViewAsync(
+ view: View,
+ attrs: WindowManager.LayoutParams,
+ configuration: Configuration,
+ touchableRegion: Region? = null,
+ )
/** Releases the underlying [View] hierarchy and removes the backing [SurfaceControl]. */
fun release(t: SurfaceControl.Transaction)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorWindowlessWindowManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorWindowlessWindowManager.kt
new file mode 100644
index 0000000..fbe8c6c8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorWindowlessWindowManager.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor.common.viewhost
+
+import android.content.res.Configuration
+import android.graphics.Region
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.WindowlessWindowManager
+
+/**
+ * A [WindowlessWindowManager] for the window decor caption that allows customizing the touchable
+ * region.
+ */
+class WindowDecorWindowlessWindowManager(
+ configuration: Configuration,
+ rootSurface: SurfaceControl,
+) : WindowlessWindowManager(configuration, rootSurface, /* hostInputTransferToken= */ null) {
+
+ /** Set the view host's touchable region. */
+ fun setTouchRegion(viewHost: SurfaceControlViewHost, region: Region?) {
+ setTouchRegion(viewHost.windowToken.asBinder(), region)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
index 310c2d7..ec3fe95 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
@@ -54,6 +54,7 @@
private final Point mPositionInParent = new Point();
private boolean mIsVisible = false;
private boolean mIsTopActivityTransparent = false;
+ private boolean mIsActivityStackTransparent = false;
private int mNumActivities = 1;
private long mLastActiveTime;
@@ -158,6 +159,12 @@
return this;
}
+ public TestRunningTaskInfoBuilder setActivityStackTransparent(
+ boolean isActivityStackTransparent) {
+ mIsActivityStackTransparent = isActivityStackTransparent;
+ return this;
+ }
+
public TestRunningTaskInfoBuilder setNumActivities(int numActivities) {
mNumActivities = numActivities;
return this;
@@ -187,6 +194,7 @@
info.positionInParent = mPositionInParent;
info.isVisible = mIsVisible;
info.isTopActivityTransparent = mIsTopActivityTransparent;
+ info.isActivityStackTransparent = mIsActivityStackTransparent;
info.numActivities = mNumActivities;
info.lastActiveTime = mLastActiveTime;
info.userId = mUserId;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
index 1d39000..d52fd4f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
@@ -37,35 +37,46 @@
@SmallTest
class AppCompatUtilsTest : ShellTestCase() {
@Test
- fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent() {
+ fun testIsTopActivityExemptFromDesktopWindowing_onlyTransparentActivitiesInStack() {
assertTrue(isTopActivityExemptFromDesktopWindowing(mContext,
createFreeformTask(/* displayId */ 0)
.apply {
- isTopActivityTransparent = true
- numActivities = 1
+ isActivityStackTransparent = true
isTopActivityNoDisplay = false
+ numActivities = 1
}))
}
@Test
- fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent_multipleActivities() {
+ fun testIsTopActivityExemptFromDesktopWindowing_noActivitiesInStack() {
assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
createFreeformTask(/* displayId */ 0)
.apply {
- isTopActivityTransparent = true
- numActivities = 2
+ isActivityStackTransparent = true
isTopActivityNoDisplay = false
+ numActivities = 0
}))
}
@Test
- fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent_notDisplayed() {
+ fun testIsTopActivityExemptFromDesktopWindowing_nonTransparentActivitiesInStack() {
assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
createFreeformTask(/* displayId */ 0)
.apply {
- isTopActivityTransparent = true
+ isActivityStackTransparent = false
+ isTopActivityNoDisplay = false
numActivities = 1
+ }))
+ }
+
+ @Test
+ fun testIsTopActivityExemptFromDesktopWindowing_transparentActivityStack_notDisplayed() {
+ assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
+ createFreeformTask(/* displayId */ 0)
+ .apply {
+ isActivityStackTransparent = true
isTopActivityNoDisplay = true
+ numActivities = 1
}))
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxConfigurationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxConfigurationTest.kt
index 75025d90..1399600 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxConfigurationTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxConfigurationTest.kt
@@ -26,6 +26,7 @@
import com.android.internal.R
import com.android.wm.shell.ShellTestCase
import java.util.function.Consumer
+import kotlin.test.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
@@ -51,6 +52,14 @@
val COLOR_WHITE_RESOURCE_ID = android.R.color.white
@JvmStatic
val COLOR_BLACK_RESOURCE_ID = android.R.color.black
+ @JvmStatic
+ val ROUNDED_CORNER_RADIUS_DEFAULT = 32
+ @JvmStatic
+ val ROUNDED_CORNER_RADIUS_VALID = 16
+ @JvmStatic
+ val ROUNDED_CORNER_RADIUS_NONE = 0
+ @JvmStatic
+ val ROUNDED_CORNER_RADIUS_INVALID = -10
}
@Test
@@ -112,6 +121,68 @@
}
}
+ @Test
+ fun `default rounded corner radius is used if override is not set`() {
+ runTestScenario { r ->
+ r.setDefaultRoundedCornerRadius(ROUNDED_CORNER_RADIUS_DEFAULT)
+ r.loadConfiguration()
+ r.checkRoundedCornersRadius(ROUNDED_CORNER_RADIUS_DEFAULT)
+ }
+ }
+
+ @Test
+ fun `new rounded corner radius is used after setting a valid value`() {
+ runTestScenario { r ->
+ r.setDefaultRoundedCornerRadius(ROUNDED_CORNER_RADIUS_DEFAULT)
+ r.loadConfiguration()
+ r.overrideRoundedCornersRadius(ROUNDED_CORNER_RADIUS_VALID)
+ r.checkRoundedCornersRadius(ROUNDED_CORNER_RADIUS_VALID)
+ }
+ }
+
+ @Test
+ fun `no rounded corner radius is used after setting an invalid value`() {
+ runTestScenario { r ->
+ r.setDefaultRoundedCornerRadius(ROUNDED_CORNER_RADIUS_DEFAULT)
+ r.loadConfiguration()
+ r.overrideRoundedCornersRadius(ROUNDED_CORNER_RADIUS_INVALID)
+ r.checkRoundedCornersRadius(ROUNDED_CORNER_RADIUS_NONE)
+ }
+ }
+
+ @Test
+ fun `has rounded corners for different values`() {
+ runTestScenario { r ->
+ r.setDefaultRoundedCornerRadius(ROUNDED_CORNER_RADIUS_DEFAULT)
+ r.loadConfiguration()
+ r.checkIsLetterboxActivityCornersRounded(true)
+
+ r.overrideRoundedCornersRadius(ROUNDED_CORNER_RADIUS_INVALID)
+ r.checkIsLetterboxActivityCornersRounded(false)
+
+ r.overrideRoundedCornersRadius(ROUNDED_CORNER_RADIUS_NONE)
+ r.checkIsLetterboxActivityCornersRounded(false)
+
+ r.overrideRoundedCornersRadius(ROUNDED_CORNER_RADIUS_VALID)
+ r.checkIsLetterboxActivityCornersRounded(true)
+ }
+ }
+
+ @Test
+ fun `reset rounded corners radius`() {
+ runTestScenario { r ->
+ r.setDefaultRoundedCornerRadius(ROUNDED_CORNER_RADIUS_DEFAULT)
+ r.loadConfiguration()
+ r.checkRoundedCornersRadius(ROUNDED_CORNER_RADIUS_DEFAULT)
+
+ r.overrideRoundedCornersRadius(ROUNDED_CORNER_RADIUS_VALID)
+ r.checkRoundedCornersRadius(ROUNDED_CORNER_RADIUS_VALID)
+
+ r.resetRoundedCornersRadius()
+ r.checkRoundedCornersRadius(ROUNDED_CORNER_RADIUS_DEFAULT)
+ }
+ }
+
/**
* Runs a test scenario providing a Robot.
*/
@@ -135,6 +206,11 @@
.getColor(R.color.config_letterboxBackgroundColor, null)
}
+ fun setDefaultRoundedCornerRadius(radius: Int) {
+ doReturn(radius).`when`(resources)
+ .getInteger(R.integer.config_letterboxActivityCornersRadius)
+ }
+
fun loadConfiguration() {
letterboxConfig = LetterboxConfiguration(ctx)
}
@@ -147,14 +223,30 @@
letterboxConfig.resetLetterboxBackgroundColor()
}
+ fun resetRoundedCornersRadius() {
+ letterboxConfig.resetLetterboxActivityCornersRadius()
+ }
+
fun overrideBackgroundColorId(@ColorRes colorId: Int) {
letterboxConfig.setLetterboxBackgroundColorResourceId(colorId)
}
+ fun overrideRoundedCornersRadius(radius: Int) {
+ letterboxConfig.setLetterboxActivityCornersRadius(radius)
+ }
+
fun checkBackgroundColor(expected: Color) {
val colorComponents = letterboxConfig.getBackgroundColorRgbArray()
val expectedComponents = expected.components
assert(expectedComponents.contentEquals(colorComponents))
}
+
+ fun checkRoundedCornersRadius(expected: Int) {
+ assertEquals(expected, letterboxConfig.getLetterboxActivityCornersRadius())
+ }
+
+ fun checkIsLetterboxActivityCornersRounded(expected: Boolean) {
+ assertEquals(expected, letterboxConfig.isLetterboxActivityCornersRounded())
+ }
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategyTest.kt
new file mode 100644
index 0000000..50fdf45
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategyTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterbox
+
+import android.content.Context
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.MULTIPLE_SURFACES
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.SINGLE_SURFACE
+import java.util.function.Consumer
+import kotlin.test.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for [LetterboxControllerStrategy].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:LetterboxControllerStrategyTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LetterboxControllerStrategyTest : ShellTestCase() {
+
+ @Test
+ fun `LetterboxMode is MULTIPLE_SURFACES with rounded corners`() {
+ runTestScenario { r ->
+ r.configureRoundedCornerRadius(true)
+ r.configureLetterboxMode()
+ r.checkLetterboxModeIsSingle()
+ }
+ }
+
+ @Test
+ fun `LetterboxMode is MULTIPLE_SURFACES with no rounded corners`() {
+ runTestScenario { r ->
+ r.configureRoundedCornerRadius(false)
+ r.configureLetterboxMode()
+ r.checkLetterboxModeIsMultiple()
+ }
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ fun runTestScenario(consumer: Consumer<LetterboxStrategyRobotTest>) {
+ val robot = LetterboxStrategyRobotTest(mContext)
+ consumer.accept(robot)
+ }
+
+ class LetterboxStrategyRobotTest(val ctx: Context) {
+
+ companion object {
+ @JvmStatic
+ private val ROUNDED_CORNERS_TRUE = 10
+ @JvmStatic
+ private val ROUNDED_CORNERS_FALSE = 0
+ }
+
+ private val letterboxConfiguration: LetterboxConfiguration
+ private val letterboxStrategy: LetterboxControllerStrategy
+
+ init {
+ letterboxConfiguration = LetterboxConfiguration(ctx)
+ letterboxStrategy = LetterboxControllerStrategy(letterboxConfiguration)
+ }
+
+ fun configureRoundedCornerRadius(enabled: Boolean) {
+ letterboxConfiguration.setLetterboxActivityCornersRadius(
+ if (enabled) ROUNDED_CORNERS_TRUE else ROUNDED_CORNERS_FALSE
+ )
+ }
+
+ fun configureLetterboxMode() {
+ letterboxStrategy.configureLetterboxMode()
+ }
+
+ fun checkLetterboxModeIsSingle(expected: Boolean = true) {
+ val expectedMode = if (expected) SINGLE_SURFACE else MULTIPLE_SURFACES
+ assertEquals(expectedMode, letterboxStrategy.getLetterboxImplementationMode())
+ }
+
+ fun checkLetterboxModeIsMultiple(expected: Boolean = true) {
+ val expectedMode = if (expected) MULTIPLE_SURFACES else SINGLE_SURFACE
+ assertEquals(expectedMode, letterboxStrategy.getLetterboxImplementationMode())
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index c10434a..3bee588 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -1151,7 +1151,7 @@
fun moveRunningTaskToDesktop_topActivityTranslucentWithoutDisplay_taskIsMovedToDesktop() {
val task =
setUpFullscreenTask().apply {
- isTopActivityTransparent = true
+ isActivityStackTransparent = true
isTopActivityNoDisplay = true
numActivities = 1
}
@@ -1167,7 +1167,7 @@
fun moveRunningTaskToDesktop_topActivityTranslucentWithDisplay_doesNothing() {
val task =
setUpFullscreenTask().apply {
- isTopActivityTransparent = true
+ isActivityStackTransparent = true
isTopActivityNoDisplay = false
numActivities = 1
}
@@ -2260,7 +2260,7 @@
val task =
setUpFullscreenTask().apply {
- isTopActivityTransparent = true
+ isActivityStackTransparent = true
isTopActivityNoDisplay = true
numActivities = 1
}
@@ -2278,7 +2278,7 @@
val task =
setUpFreeformTask().apply {
- isTopActivityTransparent = true
+ isActivityStackTransparent = true
isTopActivityNoDisplay = false
numActivities = 1
}
@@ -3305,44 +3305,41 @@
setUpLandscapeDisplay()
val task = setUpFreeformTask()
val taskToRequest = setUpFreeformTask()
- val wctCaptor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
runOpenInstance(task, taskToRequest.taskId)
- verify(transitions).startTransition(anyInt(), wctCaptor.capture(), anyOrNull())
- assertThat(ActivityOptions.fromBundle(wctCaptor.value.hierarchyOps[0].launchOptions)
- .launchWindowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
+ verify(desktopMixedTransitionHandler).startLaunchTransition(anyInt(), any(), anyInt(),
+ anyOrNull(), anyOrNull())
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT)
+ assertThat(wct.hierarchyOps).hasSize(1)
+ wct.assertReorderAt(index = 0, taskToRequest)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
fun openInstance_fromFreeform_minimizesIfNeeded() {
setUpLandscapeDisplay()
- val homeTask = setUpHomeTask()
val freeformTasks = (1..MAX_TASK_LIMIT + 1).map { _ -> setUpFreeformTask() }
val oldestTask = freeformTasks.first()
val newestTask = freeformTasks.last()
+ val transition = Binder()
+ val wctCaptor = argumentCaptor<WindowContainerTransaction>()
+ whenever(desktopMixedTransitionHandler.startLaunchTransition(anyInt(), wctCaptor.capture(),
+ anyInt(), anyOrNull(), anyOrNull()
+ ))
+ .thenReturn(transition)
+
runOpenInstance(newestTask, freeformTasks[1].taskId)
- val wct = getLatestWct(type = TRANSIT_OPEN)
- // Home is moved to front of everything.
- assertThat(
- wct.hierarchyOps.any { hop ->
- hop.container == homeTask.token.asBinder() && hop.toTop
- }
- ).isTrue()
- // And the oldest task isn't moved in front of home, effectively minimizing it.
- assertThat(
- wct.hierarchyOps.none { hop ->
- hop.container == oldestTask.token.asBinder() && hop.toTop
- }
- ).isTrue()
+ val wct = wctCaptor.firstValue
+ assertThat(wct.hierarchyOps.size).isEqualTo(2) // move-to-front + minimize
+ wct.assertReorderAt(0, freeformTasks[1], toTop = true)
+ wct.assertReorderAt(1, oldestTask, toTop = false)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
fun openInstance_fromFreeform_exitsImmersiveIfNeeded() {
setUpLandscapeDisplay()
- val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
val immersiveTask = setUpFreeformTask()
taskRepository.setTaskInFullImmersiveState(
@@ -3352,11 +3349,13 @@
)
val runOnStartTransit = RunOnStartTransitionCallback()
val transition = Binder()
- whenever(transitions.startTransition(eq(TRANSIT_OPEN), any(), anyOrNull()))
+ whenever(desktopMixedTransitionHandler.startLaunchTransition(anyInt(), any(), anyInt(),
+ anyOrNull(), anyOrNull()
+ ))
.thenReturn(transition)
whenever(mMockDesktopImmersiveController
.exitImmersiveIfApplicable(
- any(), eq(immersiveTask.displayId), eq(freeformTask.taskId), any()))
+ any(), eq(DEFAULT_DISPLAY), eq(freeformTask.taskId), any()))
.thenReturn(
ExitResult.Exit(
exitingTask = immersiveTask.taskId,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
index 866d1b3..aee8821 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
@@ -73,10 +73,10 @@
.setLastActiveTime(100)
.build()
- /** Create a new System Modal task, i.e. a task with a single transparent activity. */
+ /** Create a new System Modal task, i.e. a task with only transparent activities. */
fun createSystemModalTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo =
createFullscreenTaskBuilder(displayId)
- .setTopActivityTransparent(true)
+ .setActivityStackTransparent(true)
.setNumActivities(1)
.build()
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
index b9d7bbf..c33005e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
@@ -43,6 +43,10 @@
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
+/**
+ * Tests for {@link SystemModalsTransitionHandler}
+ * Usage: atest WMShellUnitTests:SystemModalsTransitionHandlerTest
+ */
@SmallTest
@RunWith(AndroidTestingRunner::class)
class SystemModalsTransitionHandlerTest : ShellTestCase() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index a4e3af4..117c85e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -278,7 +278,7 @@
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
fun testDecorationIsNotCreatedForTopTranslucentActivities() {
val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply {
- isTopActivityTransparent = true
+ isActivityStackTransparent = true
isTopActivityNoDisplay = false
numActivities = 1
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index afd4607..dce44b7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -74,6 +74,8 @@
import com.android.wm.shell.util.StubTransaction
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeKeyguardChangeListener
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder
import org.junit.After
import org.junit.Rule
@@ -131,6 +133,8 @@
protected val mockAssistContentRequester = mock<AssistContentRequester>()
protected val bgExecutor = TestShellExecutor()
protected val mockMultiInstanceHelper = mock<MultiInstanceHelper>()
+ private val mockWindowDecorViewHostSupplier =
+ mock<WindowDecorViewHostSupplier<WindowDecorViewHost>>()
protected val mockTasksLimiter = mock<DesktopTasksLimiter>()
protected val mockFreeformTaskTransitionStarter = mock<FreeformTaskTransitionStarter>()
protected val mockActivityOrientationChangeHandler =
@@ -193,6 +197,7 @@
mockDesktopImmersiveController,
mockGenericLinksParser,
mockAssistContentRequester,
+ mockWindowDecorViewHostSupplier,
mockMultiInstanceHelper,
mockDesktopModeWindowDecorFactory,
mockInputMonitorFactory,
@@ -289,7 +294,7 @@
whenever(
mockDesktopModeWindowDecorFactory.create(
any(), any(), any(), any(), any(), any(), eq(task), any(), any(), any(), any(),
- any(), any(), any(), any(), any(), any(), any(), any())
+ any(), any(), any(), any(), any(), any(), any(), any(), any())
).thenReturn(decoration)
decoration.mTaskInfo = task
whenever(decoration.user).thenReturn(mockUserHandle)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index f4cd8e0..5d5d1f2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -39,7 +39,6 @@
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
@@ -114,6 +113,8 @@
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
import kotlin.Unit;
@@ -186,6 +187,10 @@
@Mock
private AttachedSurfaceControl mMockRootSurfaceControl;
@Mock
+ private WindowDecorViewHostSupplier<WindowDecorViewHost> mMockWindowDecorViewHostSupplier;
+ @Mock
+ private WindowDecorViewHost mMockWindowDecorViewHost;
+ @Mock
private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory;
@Mock
private TypedArray mMockRoundedCornersRadiusArray;
@@ -275,6 +280,9 @@
any())).thenReturn(mMockAppHeaderViewHolder);
when(mMockDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository);
when(mMockDesktopUserRepositories.getProfile(anyInt())).thenReturn(mDesktopRepository);
+ when(mMockWindowDecorViewHostSupplier.acquire(any(), eq(defaultDisplay)))
+ .thenReturn(mMockWindowDecorViewHost);
+ when(mMockWindowDecorViewHost.getSurfaceControl()).thenReturn(mock(SurfaceControl.class));
}
@After
@@ -1327,68 +1335,41 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
- public void capturedLink_postsOnCapturedLinkExpiredRunnable() {
- final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
- final DesktopModeWindowDecoration decor = createWindowDecoration(
- taskInfo, TEST_URI1 /* captured link */, null /* web uri */,
- null /* generic link */);
- final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
-
- // Run runnable to set captured link to expired
- verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
- runnableArgument.getValue().run();
-
- // Verify captured link is no longer valid by verifying link is not set as handle menu
- // browser link.
- createHandleMenu(decor);
- verifyHandleMenuCreated(null /* uri */);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
public void capturedLink_capturedLinkNotResetToSameLink() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
final DesktopModeWindowDecoration decor = createWindowDecoration(
taskInfo, TEST_URI1 /* captured link */, null /* web uri */,
null /* generic link */);
- final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
+ final ArgumentCaptor<Function1<Intent, Unit>> openInBrowserCaptor =
+ ArgumentCaptor.forClass(Function1.class);
- // Run runnable to set captured link to expired
- verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
- runnableArgument.getValue().run();
+ createHandleMenu(decor);
+ verify(mMockHandleMenu).show(any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ openInBrowserCaptor.capture(),
+ any(),
+ any(),
+ any(),
+ anyBoolean()
+ );
+ // Run runnable to set captured link to used
+ openInBrowserCaptor.getValue().invoke(new Intent(Intent.ACTION_MAIN, TEST_URI1));
// Relayout decor with same captured link
decor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
- // Verify handle menu's browser link not set to captured link since link is expired
+ // Verify handle menu's browser link not set to captured link since link is already used
createHandleMenu(decor);
verifyHandleMenuCreated(null /* uri */);
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
- public void capturedLink_capturedLinkStillUsedIfExpiredAfterHandleMenuCreation() {
- final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
- final DesktopModeWindowDecoration decor = createWindowDecoration(
- taskInfo, TEST_URI1 /* captured link */, null /* web uri */,
- null /* generic link */);
- final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
-
- // Create handle menu before link expires
- createHandleMenu(decor);
-
- // Run runnable to set captured link to expired
- verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
- runnableArgument.getValue().run();
-
- // Verify handle menu's browser link is set to captured link since menu was opened before
- // captured link expired
- verifyHandleMenuCreated(TEST_URI1);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
- public void capturedLink_capturedLinkExpiresAfterClick() {
+ public void capturedLink_capturedLinkSetToUsedAfterClick() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
final DesktopModeWindowDecoration decor = createWindowDecoration(
taskInfo, TEST_URI1 /* captured link */, null /* web uri */,
@@ -1750,7 +1731,7 @@
mMockGenericLinksParser, mMockAssistContentRequester, SurfaceControl.Builder::new,
mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new,
new WindowManagerWrapper(mMockWindowManager), mMockSurfaceControlViewHostFactory,
- maximizeMenuFactory, mMockHandleMenuFactory,
+ mMockWindowDecorViewHostSupplier, maximizeMenuFactory, mMockHandleMenuFactory,
mMockMultiInstanceHelper, mMockCaptionHandleRepository, mDesktopModeEventLogger);
windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener,
mMockTouchEventListener, mMockTouchEventListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 3a82ff6..d969346 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -89,6 +89,8 @@
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.tests.R;
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import org.junit.Before;
import org.junit.Rule;
@@ -131,6 +133,10 @@
@Mock
private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory;
@Mock
+ private WindowDecorViewHostSupplier<WindowDecorViewHost> mMockWindowDecorViewHostSupplier;
+ @Mock
+ private WindowDecorViewHost mMockWindowDecorViewHost;
+ @Mock
private SurfaceControlViewHost mMockSurfaceControlViewHost;
@Mock
private AttachedSurfaceControl mMockRootSurfaceControl;
@@ -180,6 +186,10 @@
// Add status bar inset so that WindowDecoration does not think task is in immersive mode
mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars()).setVisible(true);
doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
+
+ when(mMockWindowDecorViewHostSupplier.acquire(any(), any()))
+ .thenReturn(mMockWindowDecorViewHost);
+ when(mMockWindowDecorViewHost.getSurfaceControl()).thenReturn(mock(SurfaceControl.class));
}
@Test
@@ -236,10 +246,6 @@
final SurfaceControl.Builder decorContainerSurfaceBuilder =
createMockSurfaceControlBuilder(decorContainerSurface);
mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
- final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
- final SurfaceControl.Builder captionContainerSurfaceBuilder =
- createMockSurfaceControlBuilder(captionContainerSurface);
- mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
.setDisplayId(Display.DEFAULT_DISPLAY)
@@ -260,18 +266,19 @@
verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true);
verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 300, 100);
- verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
- verify(captionContainerSurfaceBuilder).setContainerLayer();
+ final SurfaceControl captionContainerSurface = mMockWindowDecorViewHost.getSurfaceControl();
+ verify(mMockSurfaceControlStartT).reparent(captionContainerSurface, decorContainerSurface);
verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
verify(mMockSurfaceControlStartT).show(captionContainerSurface);
- verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
-
- verify(mMockSurfaceControlViewHost)
- .setView(same(mMockView),
- argThat(lp -> lp.height == 64
- && lp.width == 300
- && (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0));
+ verify(mMockWindowDecorViewHost).updateView(
+ same(mMockView),
+ argThat(lp -> lp.height == 64
+ && lp.width == 300
+ && (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0),
+ eq(taskInfo.configuration),
+ any(),
+ eq(null) /* onDrawTransaction */);
verify(mMockView).setTaskFocusState(true);
verify(mMockWindowContainerTransaction).addInsetsSource(
eq(taskInfo.token),
@@ -300,10 +307,6 @@
final SurfaceControl.Builder decorContainerSurfaceBuilder =
createMockSurfaceControlBuilder(decorContainerSurface);
mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
- final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
- final SurfaceControl.Builder captionContainerSurfaceBuilder =
- createMockSurfaceControlBuilder(captionContainerSurface);
- mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
mMockSurfaceControlTransactions.add(t);
@@ -326,7 +329,7 @@
windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
- verify(mMockSurfaceControlViewHost, never()).release();
+ verify(mMockWindowDecorViewHost, never()).release(any());
verify(t, never()).apply();
verify(mMockWindowContainerTransaction, never())
.removeInsetsSource(eq(taskInfo.token), any(), anyInt(), anyInt());
@@ -336,9 +339,8 @@
taskInfo.isVisible = false;
windowDecor.relayout(taskInfo, false /* hasGlobalFocus */);
- final InOrder releaseOrder = inOrder(t2, mMockSurfaceControlViewHost);
- releaseOrder.verify(mMockSurfaceControlViewHost).release();
- releaseOrder.verify(t2).remove(captionContainerSurface);
+ final InOrder releaseOrder = inOrder(t2, mMockWindowDecorViewHostSupplier);
+ releaseOrder.verify(mMockWindowDecorViewHostSupplier).release(mMockWindowDecorViewHost, t2);
releaseOrder.verify(t2).remove(decorContainerSurface);
releaseOrder.verify(t2).apply();
// Expect to remove two insets sources, the caption insets and the mandatory gesture insets.
@@ -386,8 +388,8 @@
verify(mMockDisplayController).removeDisplayWindowListener(same(listener));
assertThat(mRelayoutResult.mRootView).isSameInstanceAs(mMockView);
- verify(mMockSurfaceControlViewHostFactory).create(any(), eq(mockDisplay), any());
- verify(mMockSurfaceControlViewHost).setView(same(mMockView), any());
+ verify(mMockWindowDecorViewHostSupplier).acquire(any(), eq(mockDisplay));
+ verify(mMockWindowDecorViewHost).updateView(same(mMockView), any(), any(), any(), any());
}
@Test
@@ -400,10 +402,6 @@
final SurfaceControl.Builder decorContainerSurfaceBuilder =
createMockSurfaceControlBuilder(decorContainerSurface);
mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
- final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
- final SurfaceControl.Builder captionContainerSurfaceBuilder =
- createMockSurfaceControlBuilder(captionContainerSurface);
- mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
mMockSurfaceControlTransactions.add(t);
@@ -439,8 +437,7 @@
windowDecor.mDecorWindowContext.getResources(), mRelayoutParams.mCaptionHeightId);
verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, width, height);
verify(mMockSurfaceControlAddWindowT).show(additionalWindowSurface);
- verify(mMockSurfaceControlViewHostFactory, Mockito.times(2))
- .create(any(), eq(defaultDisplay), any());
+ verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
}
@Test
@@ -453,10 +450,6 @@
final SurfaceControl.Builder decorContainerSurfaceBuilder =
createMockSurfaceControlBuilder(decorContainerSurface);
mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
- final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
- final SurfaceControl.Builder captionContainerSurfaceBuilder =
- createMockSurfaceControlBuilder(captionContainerSurface);
- mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
mMockSurfaceControlTransactions.add(t);
@@ -475,8 +468,8 @@
windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
- verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
- verify(captionContainerSurfaceBuilder).setContainerLayer();
+ final SurfaceControl captionContainerSurface = mMockWindowDecorViewHost.getSurfaceControl();
+ verify(mMockSurfaceControlStartT).reparent(captionContainerSurface, decorContainerSurface);
// Width of the captionContainerSurface should match the width of TASK_BOUNDS
verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
verify(mMockSurfaceControlStartT).show(captionContainerSurface);
@@ -492,10 +485,6 @@
final SurfaceControl.Builder decorContainerSurfaceBuilder =
createMockSurfaceControlBuilder(decorContainerSurface);
mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
- final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
- final SurfaceControl.Builder captionContainerSurfaceBuilder =
- createMockSurfaceControlBuilder(captionContainerSurface);
- mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
mMockSurfaceControlTransactions.add(t);
@@ -512,10 +501,11 @@
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
- windowDecor.relayout(taskInfo, true /* applyStartTransactionOnDraw */,
- true /* hasGlobalFocus */, Region.obtain());
+ mRelayoutParams.mApplyStartTransactionOnDraw = true;
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */, Region.obtain());
- verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT);
+ verify(mMockWindowDecorViewHost).updateView(any(), any(), any(), any(),
+ eq(mMockSurfaceControlStartT));
}
@Test
@@ -918,7 +908,13 @@
/* hasGlobalFocus= */ true,
Region.obtain());
- verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT);
+ verify(mMockWindowDecorViewHost)
+ .updateView(
+ eq(mRelayoutResult.mRootView),
+ any(),
+ eq(windowDecor.mTaskInfo.configuration),
+ any(),
+ eq(mMockSurfaceControlStartT));
windowDecor.close();
}
@@ -933,15 +929,11 @@
mRelayoutParams.mAsyncViewHost = true;
mRelayoutResult.mRootView = mMockView;
- windowDecor.relayout(
- windowDecor.mTaskInfo,
- /* hasGlobalFocus= */ true,
- Region.obtain());
- final ArgumentCaptor<Runnable> updateViewHostCaptor =
- ArgumentCaptor.forClass(Runnable.class);
- verify(mMockHandler).post(updateViewHostCaptor.capture());
assertThrows(IllegalArgumentException.class,
- () -> updateViewHostCaptor.getValue().run());
+ () -> windowDecor.relayout(
+ windowDecor.mTaskInfo,
+ /* hasGlobalFocus= */ true,
+ Region.obtain()));
windowDecor.close();
}
@@ -961,13 +953,9 @@
/* hasGlobalFocus= */ true,
Region.obtain());
- final ArgumentCaptor<Runnable> updateViewHostCaptor =
- ArgumentCaptor.forClass(Runnable.class);
- verify(mMockHandler).post(updateViewHostCaptor.capture());
-
- updateViewHostCaptor.getValue().run();
-
- verify(mMockSurfaceControlViewHost).setView(eq(mMockView), any());
+ verify(mMockWindowDecorViewHost)
+ .updateViewAsync(eq(mRelayoutResult.mRootView), any(),
+ eq(windowDecor.mTaskInfo.configuration), any());
windowDecor.close();
}
@@ -1046,13 +1034,14 @@
private TestWindowDecoration createWindowDecoration(ActivityManager.RunningTaskInfo taskInfo) {
return new TestWindowDecoration(mContext, mContext, mMockDisplayController,
- mMockShellTaskOrganizer, mMockHandler, taskInfo, mMockTaskSurface,
+ mMockShellTaskOrganizer, taskInfo, mMockTaskSurface,
new MockObjectSupplier<>(mMockSurfaceControlBuilders,
() -> createMockSurfaceControlBuilder(mock(SurfaceControl.class))),
new MockObjectSupplier<>(mMockSurfaceControlTransactions,
() -> mock(SurfaceControl.Transaction.class)),
() -> mMockWindowContainerTransaction, () -> mMockTaskSurface,
- mMockSurfaceControlViewHostFactory, mDesktopModeEventLogger);
+ mMockSurfaceControlViewHostFactory, mMockWindowDecorViewHostSupplier,
+ mDesktopModeEventLogger);
}
private class MockObjectSupplier<T> implements Supplier<T> {
@@ -1087,7 +1076,6 @@
TestWindowDecoration(Context context, @NonNull Context userContext,
DisplayController displayController,
ShellTaskOrganizer taskOrganizer,
- Handler handler,
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
@@ -1095,11 +1083,14 @@
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
Supplier<SurfaceControl> surfaceControlSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost>
+ windowDecorViewHostSupplier,
DesktopModeEventLogger desktopModeEventLogger) {
- super(context, userContext, displayController, taskOrganizer, handler, taskInfo,
+ super(context, userContext, displayController, taskOrganizer, taskInfo,
taskSurface, surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
windowContainerTransactionSupplier, surfaceControlSupplier,
- surfaceControlViewHostFactory, desktopModeEventLogger);
+ surfaceControlViewHostFactory, windowDecorViewHostSupplier,
+ desktopModeEventLogger);
}
void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus) {
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index dd5067a..a3ad340 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -49,6 +49,7 @@
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
+import java.util.stream.Collectors;
/**
* Describes the properties of a route.
@@ -189,6 +190,7 @@
* the device.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_BUILTIN_SPEAKER
*/
public static final int TYPE_BUILTIN_SPEAKER = AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
@@ -196,6 +198,7 @@
* Indicates the route is a headset, which is the combination of a headphones and a microphone.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_WIRED_HEADSET
*/
public static final int TYPE_WIRED_HEADSET = AudioDeviceInfo.TYPE_WIRED_HEADSET;
@@ -203,6 +206,7 @@
* Indicates the route is a pair of wired headphones.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_WIRED_HEADPHONES
*/
public static final int TYPE_WIRED_HEADPHONES = AudioDeviceInfo.TYPE_WIRED_HEADPHONES;
@@ -210,6 +214,7 @@
* Indicates the route is a bluetooth device, such as a bluetooth speaker or headphones.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_BLUETOOTH_A2DP
*/
public static final int TYPE_BLUETOOTH_A2DP = AudioDeviceInfo.TYPE_BLUETOOTH_A2DP;
@@ -217,6 +222,7 @@
* Indicates the route is an HDMI connection.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_HDMI
*/
public static final int TYPE_HDMI = AudioDeviceInfo.TYPE_HDMI;
@@ -224,6 +230,7 @@
* Indicates the route is an Audio Return Channel of an HDMI connection.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_HDMI_ARC
*/
@FlaggedApi(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
public static final int TYPE_HDMI_ARC = AudioDeviceInfo.TYPE_HDMI_ARC;
@@ -232,24 +239,34 @@
* Indicates the route is an Enhanced Audio Return Channel of an HDMI connection.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_HDMI_EARC
*/
@FlaggedApi(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
public static final int TYPE_HDMI_EARC = AudioDeviceInfo.TYPE_HDMI_EARC;
/**
* Indicates the route is a digital line connection (for example S/PDIF).
+ *
+ * @see #getType
+ * @see AudioDeviceInfo#TYPE_LINE_DIGITAL
*/
@FlaggedApi(FLAG_ENABLE_NEW_WIRED_MEDIA_ROUTE_2_INFO_TYPES)
public static final int TYPE_LINE_DIGITAL = AudioDeviceInfo.TYPE_LINE_DIGITAL;
/**
* Indicates the route is an analog line-level connection.
+ *
+ * @see #getType
+ * @see AudioDeviceInfo#TYPE_LINE_ANALOG
*/
@FlaggedApi(FLAG_ENABLE_NEW_WIRED_MEDIA_ROUTE_2_INFO_TYPES)
public static final int TYPE_LINE_ANALOG = AudioDeviceInfo.TYPE_LINE_ANALOG;
/**
* Indicates the route is using the auxiliary line-level connectors.
+ *
+ * @see #getType
+ * @see AudioDeviceInfo#TYPE_AUX_LINE
*/
@FlaggedApi(FLAG_ENABLE_NEW_WIRED_MEDIA_ROUTE_2_INFO_TYPES)
public static final int TYPE_AUX_LINE = AudioDeviceInfo.TYPE_AUX_LINE;
@@ -258,6 +275,7 @@
* Indicates the route is a USB audio device.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_USB_DEVICE
*/
public static final int TYPE_USB_DEVICE = AudioDeviceInfo.TYPE_USB_DEVICE;
@@ -265,6 +283,7 @@
* Indicates the route is a USB audio device in accessory mode.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_USB_ACCESSORY
*/
public static final int TYPE_USB_ACCESSORY = AudioDeviceInfo.TYPE_USB_ACCESSORY;
@@ -272,6 +291,7 @@
* Indicates the route is the audio device associated with a dock.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_DOCK
*/
public static final int TYPE_DOCK = AudioDeviceInfo.TYPE_DOCK;
@@ -279,6 +299,7 @@
* Indicates the route is a USB audio headset.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_USB_HEADSET
*/
public static final int TYPE_USB_HEADSET = AudioDeviceInfo.TYPE_USB_HEADSET;
@@ -286,6 +307,7 @@
* Indicates the route is a hearing aid.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_HEARING_AID
*/
public static final int TYPE_HEARING_AID = AudioDeviceInfo.TYPE_HEARING_AID;
@@ -293,6 +315,7 @@
* Indicates the route is a Bluetooth Low Energy (BLE) HEADSET.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_BLE_HEADSET
*/
public static final int TYPE_BLE_HEADSET = AudioDeviceInfo.TYPE_BLE_HEADSET;
@@ -304,6 +327,7 @@
* to provide a better experience on multichannel contents.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_MULTICHANNEL_GROUP
*/
@FlaggedApi(FLAG_ENABLE_MULTICHANNEL_GROUP_DEVICE)
public static final int TYPE_MULTICHANNEL_SPEAKER_GROUP =
@@ -617,7 +641,7 @@
private final String mProviderId;
private final boolean mIsVisibilityRestricted;
private final Set<String> mAllowedPackages;
- private final Set<String> mRequiredPermissions;
+ private final List<Set<String>> mRequiredPermissions;
@SuitabilityStatus private final int mSuitabilityStatus;
MediaRoute2Info(@NonNull Builder builder) {
@@ -642,7 +666,7 @@
mIsVisibilityRestricted = builder.mIsVisibilityRestricted;
mAllowedPackages = builder.mAllowedPackages;
mSuitabilityStatus = builder.mSuitabilityStatus;
- mRequiredPermissions = Set.copyOf(builder.mRequiredPermissions);
+ mRequiredPermissions = List.copyOf(builder.mRequiredPermissions);
}
MediaRoute2Info(@NonNull Parcel in) {
@@ -667,7 +691,12 @@
mProviderId = in.readString();
mIsVisibilityRestricted = in.readBoolean();
mAllowedPackages = Set.of(in.createString8Array());
- mRequiredPermissions = Set.of(in.createString8Array());
+ ArrayList<Set<String>> requiredPermissions = new ArrayList<>();
+ int numRequiredPermissionSets = in.readInt();
+ for (int i = 0; i < numRequiredPermissionSets; i++) {
+ requiredPermissions.add(Set.of(in.createString8Array()));
+ }
+ mRequiredPermissions = List.copyOf(requiredPermissions); // Use copyOf to make it immutable.
mSuitabilityStatus = in.readInt();
}
@@ -911,11 +940,12 @@
}
/**
- * @return the set of permissions which must be held to see this route
+ * @return a list of permission sets - all the permissions in at least one of these sets must be
+ * held to see this route.
*/
@NonNull
@FlaggedApi(FLAG_ENABLE_ROUTE_VISIBILITY_CONTROL_API)
- public Set<String> getRequiredPermissions() {
+ public List<Set<String>> getRequiredPermissions() {
return mRequiredPermissions;
}
@@ -1096,7 +1126,8 @@
.append(", allowedPackages=")
.append(String.join(",", mAllowedPackages))
.append(", mRequiredPermissions=")
- .append(String.join(",", mRequiredPermissions))
+ .append(mRequiredPermissions.stream().map(set -> String.join(",", set)).collect(
+ Collectors.joining("),(", "(", ")")))
.append(", suitabilityStatus=")
.append(mSuitabilityStatus)
.append(" }")
@@ -1130,7 +1161,10 @@
dest.writeString(mProviderId);
dest.writeBoolean(mIsVisibilityRestricted);
dest.writeString8Array(mAllowedPackages.toArray(new String[0]));
- dest.writeString8Array(mRequiredPermissions.toArray(new String[0]));
+ dest.writeInt(mRequiredPermissions.size());
+ for (Set<String> permissionSet : mRequiredPermissions) {
+ dest.writeString8Array(permissionSet.toArray(new String[0]));
+ }
dest.writeInt(mSuitabilityStatus);
}
@@ -1279,7 +1313,7 @@
private String mProviderId;
private boolean mIsVisibilityRestricted;
private Set<String> mAllowedPackages;
- private Set<String> mRequiredPermissions;
+ private List<Set<String>> mRequiredPermissions;
@SuitabilityStatus private int mSuitabilityStatus;
/**
@@ -1305,7 +1339,7 @@
mDeduplicationIds = Set.of();
mAllowedPackages = Set.of();
mSuitabilityStatus = SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER;
- mRequiredPermissions = Set.of();
+ mRequiredPermissions = List.of();
}
/**
@@ -1587,7 +1621,7 @@
public Builder setVisibilityPublic() {
mIsVisibilityRestricted = false;
mAllowedPackages = Set.of();
- mRequiredPermissions = Set.of();
+ mRequiredPermissions = List.of();
return this;
}
@@ -1614,13 +1648,31 @@
/**
* Limits the visibility of this route to holders of a set of permissions.
*
+ * <p>Calls to this method override any previous calls of
+ * {@link #setRequiredPermissions(Set)} or {@link #setRequiredPermissions(List)}.
+ *
* @param requiredPermissions the list of all permissions which must be held in order to
* see this route.
*/
@NonNull
@FlaggedApi(FLAG_ENABLE_ROUTE_VISIBILITY_CONTROL_API)
public Builder setRequiredPermissions(@NonNull Set<String> requiredPermissions) {
- mRequiredPermissions = Set.copyOf(requiredPermissions);
+ return setRequiredPermissions(List.of(requiredPermissions));
+ }
+
+ /**
+ * Limits the visibility of this route to holders of one of a set of permissions.
+ *
+ * <p>Calls to this method override any previous calls of
+ * {@link #setRequiredPermissions(Set)} or {@link #setRequiredPermissions(List)}.
+ *
+ * @param requiresOneOf a list of Sets of permissions. Holding all permissions in at least
+ * one of the Sets is required for the route to be visible.
+ */
+ @NonNull
+ @FlaggedApi(FLAG_ENABLE_ROUTE_VISIBILITY_CONTROL_API)
+ public Builder setRequiredPermissions(@NonNull List<Set<String>> requiresOneOf) {
+ mRequiredPermissions = List.copyOf(requiresOneOf);
return this;
}
diff --git a/media/java/android/media/quality/IMediaQualityManager.aidl b/media/java/android/media/quality/IMediaQualityManager.aidl
index 70211ca..9daebca 100644
--- a/media/java/android/media/quality/IMediaQualityManager.aidl
+++ b/media/java/android/media/quality/IMediaQualityManager.aidl
@@ -23,6 +23,7 @@
import android.media.quality.ParamCapability;
import android.media.quality.PictureProfileHandle;
import android.media.quality.PictureProfile;
+import android.media.quality.SoundProfileHandle;
import android.media.quality.SoundProfile;
/**
@@ -40,7 +41,7 @@
List<String> getPictureProfilePackageNames(int userId);
List<String> getPictureProfileAllowList(int userId);
void setPictureProfileAllowList(in List<String> packages, int userId);
- PictureProfileHandle getPictureProfileHandle(in String id, int userId);
+ List<PictureProfileHandle> getPictureProfileHandle(in String[] id, int userId);
SoundProfile createSoundProfile(in SoundProfile pp, int userId);
void updateSoundProfile(in String id, in SoundProfile pp, int userId);
@@ -52,6 +53,7 @@
List<String> getSoundProfilePackageNames(int userId);
List<String> getSoundProfileAllowList(int userId);
void setSoundProfileAllowList(in List<String> packages, int userId);
+ List<SoundProfileHandle> getSoundProfileHandle(in String[] id, int userId);
void registerPictureProfileCallback(in IPictureProfileCallback cb);
void registerSoundProfileCallback(in ISoundProfileCallback cb);
diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java
index 9d66086..efbe47b 100644
--- a/media/java/android/media/quality/MediaQualityManager.java
+++ b/media/java/android/media/quality/MediaQualityManager.java
@@ -295,7 +295,7 @@
* Gets picture profile handle by profile ID.
* @hide
*/
- public PictureProfileHandle getPictureProfileHandle(String id) {
+ public List<PictureProfileHandle> getPictureProfileHandle(String[] id) {
try {
return mService.getPictureProfileHandle(id, mUserId);
} catch (RemoteException e) {
@@ -304,6 +304,18 @@
}
/**
+ * Gets sound profile handle by profile ID.
+ * @hide
+ */
+ public List<SoundProfileHandle> getSoundProfileHandle(String[] id) {
+ try {
+ return mService.getSoundProfileHandle(id, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Creates a picture profile and store it in the system.
*
* <p>If the profile is created successfully,
diff --git a/media/java/android/media/quality/PictureProfile.java b/media/java/android/media/quality/PictureProfile.java
index dcb4222..6064485 100644
--- a/media/java/android/media/quality/PictureProfile.java
+++ b/media/java/android/media/quality/PictureProfile.java
@@ -48,6 +48,7 @@
private final String mPackageName;
@NonNull
private final PersistableBundle mParams;
+ private final PictureProfileHandle mHandle;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -121,6 +122,7 @@
mInputId = in.readString();
mPackageName = in.readString();
mParams = in.readPersistableBundle();
+ mHandle = in.readParcelable(PictureProfileHandle.class.getClassLoader());
}
@Override
@@ -131,6 +133,7 @@
dest.writeString(mInputId);
dest.writeString(mPackageName);
dest.writePersistableBundle(mParams);
+ dest.writeParcelable(mHandle, flags);
}
@Override
@@ -163,13 +166,15 @@
@NonNull String name,
@Nullable String inputId,
@NonNull String packageName,
- @NonNull PersistableBundle params) {
+ @NonNull PersistableBundle params,
+ @NonNull PictureProfileHandle handle) {
this.mId = id;
this.mType = type;
this.mName = name;
this.mInputId = inputId;
this.mPackageName = packageName;
this.mParams = params;
+ this.mHandle = handle;
}
/**
@@ -251,6 +256,15 @@
}
/**
+ * Gets profile handle
+ * @hide
+ */
+ @NonNull
+ public PictureProfileHandle getHandle() {
+ return mHandle;
+ }
+
+ /**
* A builder for {@link PictureProfile}.
*/
public static final class Builder {
@@ -265,6 +279,7 @@
private String mPackageName;
@NonNull
private PersistableBundle mParams;
+ private PictureProfileHandle mHandle;
/**
* Creates a new Builder.
@@ -283,6 +298,7 @@
mPackageName = p.getPackageName();
mInputId = p.getInputId();
mParams = p.getParameters();
+ mHandle = p.getHandle();
}
/**
@@ -350,6 +366,16 @@
}
/**
+ * Sets profile handle.
+ * @hide
+ */
+ @NonNull
+ public Builder setHandle(@NonNull PictureProfileHandle handle) {
+ mHandle = handle;
+ return this;
+ }
+
+ /**
* Builds the instance.
*/
@NonNull
@@ -361,7 +387,8 @@
mName,
mInputId,
mPackageName,
- mParams);
+ mParams,
+ mHandle);
return o;
}
}
diff --git a/media/java/android/media/quality/SoundProfile.java b/media/java/android/media/quality/SoundProfile.java
index c7fb4dd..1dd59ab 100644
--- a/media/java/android/media/quality/SoundProfile.java
+++ b/media/java/android/media/quality/SoundProfile.java
@@ -48,6 +48,7 @@
private final String mPackageName;
@NonNull
private final PersistableBundle mParams;
+ private final SoundProfileHandle mHandle;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -120,6 +121,7 @@
mInputId = in.readString();
mPackageName = in.readString();
mParams = in.readPersistableBundle();
+ mHandle = in.readParcelable(SoundProfileHandle.class.getClassLoader());
}
@Override
@@ -130,6 +132,7 @@
dest.writeString(mInputId);
dest.writeString(mPackageName);
dest.writePersistableBundle(mParams);
+ dest.writeParcelable(mHandle, flags);
}
@Override
@@ -162,13 +165,15 @@
@NonNull String name,
@Nullable String inputId,
@NonNull String packageName,
- @NonNull PersistableBundle params) {
+ @NonNull PersistableBundle params,
+ @NonNull SoundProfileHandle handle) {
this.mId = id;
this.mType = type;
this.mName = name;
this.mInputId = inputId;
this.mPackageName = packageName;
this.mParams = params;
+ this.mHandle = handle;
}
/**
@@ -250,6 +255,15 @@
}
/**
+ * Gets profile handle
+ * @hide
+ */
+ @NonNull
+ public SoundProfileHandle getHandle() {
+ return mHandle;
+ }
+
+ /**
* A builder for {@link SoundProfile}
*/
public static final class Builder {
@@ -264,6 +278,7 @@
private String mPackageName;
@NonNull
private PersistableBundle mParams;
+ private SoundProfileHandle mHandle;
/**
* Creates a new Builder.
@@ -282,6 +297,7 @@
mPackageName = p.getPackageName();
mInputId = p.getInputId();
mParams = p.getParameters();
+ mHandle = p.getHandle();
}
/**
@@ -349,6 +365,16 @@
}
/**
+ * Sets profile handle.
+ * @hide
+ */
+ @NonNull
+ public Builder setHandle(@NonNull SoundProfileHandle handle) {
+ mHandle = handle;
+ return this;
+ }
+
+ /**
* Builds the instance.
*/
@NonNull
@@ -360,7 +386,8 @@
mName,
mInputId,
mPackageName,
- mParams);
+ mParams,
+ mHandle);
return o;
}
}
diff --git a/media/java/android/media/quality/SoundProfileHandle.aidl b/media/java/android/media/quality/SoundProfileHandle.aidl
new file mode 100644
index 0000000..6b8161c
--- /dev/null
+++ b/media/java/android/media/quality/SoundProfileHandle.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.quality;
+
+parcelable SoundProfileHandle;
diff --git a/media/java/android/media/quality/SoundProfileHandle.java b/media/java/android/media/quality/SoundProfileHandle.java
new file mode 100644
index 0000000..edb546e
--- /dev/null
+++ b/media/java/android/media/quality/SoundProfileHandle.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.quality;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A type-safe handle to a sound profile.
+ *
+ * @hide
+ */
+public final class SoundProfileHandle implements Parcelable {
+ public static final @NonNull SoundProfileHandle NONE = new SoundProfileHandle(-1000);
+
+ private final long mId;
+
+ /** @hide */
+ public SoundProfileHandle(long id) {
+ mId = id;
+ }
+
+ /** @hide */
+ public long getId() {
+ return mId;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeLong(mId);
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ public static final @NonNull Creator<SoundProfileHandle> CREATOR =
+ new Creator<SoundProfileHandle>() {
+ @Override
+ public SoundProfileHandle createFromParcel(Parcel in) {
+ return new SoundProfileHandle(in);
+ }
+
+ @Override
+ public SoundProfileHandle[] newArray(int size) {
+ return new SoundProfileHandle[size];
+ }
+ };
+
+ private SoundProfileHandle(@NonNull Parcel in) {
+ mId = in.readLong();
+ }
+}
diff --git a/native/android/Android.bp b/native/android/Android.bp
index cd6de5a..129d616 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -73,6 +73,7 @@
"surface_control.cpp",
"surface_texture.cpp",
"system_fonts.cpp",
+ "system_health.cpp",
"trace.cpp",
"thermal.cpp",
],
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 78a7ddb..8dd8830 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -320,6 +320,23 @@
ASystemFontIterator_open; # introduced=29
ASystemFontIterator_close; # introduced=29
ASystemFontIterator_next; # introduced=29
+ ASystemHealth_getCpuHeadroom; # introduced=36
+ ASystemHealth_getGpuHeadroom; # introduced=36
+ ASystemHealth_getCpuHeadroomMinIntervalMillis; # introduced=36
+ ASystemHealth_getGpuHeadroomMinIntervalMillis; # introduced=36
+ ACpuHeadroomParams_create; # introduced=36
+ ACpuHeadroomParams_destroy; # introduced=36
+ ACpuHeadroomParams_setCalculationType; # introduced=36
+ ACpuHeadroomParams_getCalculationType; # introduced=36
+ ACpuHeadroomParams_setCalculationWindowMillis; # introduced=36
+ ACpuHeadroomParams_getCalculationWindowMillis; # introduced=36
+ ACpuHeadroomParams_setTids; # introduced=36
+ AGpuHeadroomParams_create; # introduced=36
+ AGpuHeadroomParams_destroy; # introduced=36
+ AGpuHeadroomParams_setCalculationType; # introduced=36
+ AGpuHeadroomParams_getCalculationType; # introduced=36
+ AGpuHeadroomParams_setCalculationWindowMillis; # introduced=36
+ AGpuHeadroomParams_getCalculationWindowMillis; # introduced=36
AFont_close; # introduced=29
AFont_getFontFilePath; # introduced=29
AFont_getWeight; # introduced=29
diff --git a/native/android/system_health.cpp b/native/android/system_health.cpp
new file mode 100644
index 0000000..f3fa9f6
--- /dev/null
+++ b/native/android/system_health.cpp
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <aidl/android/hardware/power/CpuHeadroomParams.h>
+#include <aidl/android/hardware/power/GpuHeadroomParams.h>
+#include <aidl/android/os/CpuHeadroomParamsInternal.h>
+#include <aidl/android/os/GpuHeadroomParamsInternal.h>
+#include <aidl/android/os/IHintManager.h>
+#include <android/binder_manager.h>
+#include <android/system_health.h>
+#include <binder/IServiceManager.h>
+#include <binder/Status.h>
+
+using namespace android;
+using namespace aidl::android::os;
+namespace hal = aidl::android::hardware::power;
+
+struct ACpuHeadroomParams : public CpuHeadroomParamsInternal {};
+struct AGpuHeadroomParams : public GpuHeadroomParamsInternal {};
+
+const int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50;
+const int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000;
+const int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50;
+const int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000;
+const int CPU_HEADROOM_MAX_TID_COUNT = 5;
+
+struct ASystemHealthManager {
+public:
+ static ASystemHealthManager* getInstance();
+ ASystemHealthManager(std::shared_ptr<IHintManager>& hintManager);
+ ASystemHealthManager() = delete;
+ ~ASystemHealthManager();
+ int getCpuHeadroom(const ACpuHeadroomParams* params, float* outHeadroom);
+ int getGpuHeadroom(const AGpuHeadroomParams* params, float* outHeadroom);
+ int getCpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis);
+ int getGpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis);
+
+private:
+ static ASystemHealthManager* create(std::shared_ptr<IHintManager> hintManager);
+ std::shared_ptr<IHintManager> mHintManager;
+};
+
+ASystemHealthManager* ASystemHealthManager::getInstance() {
+ static std::once_flag creationFlag;
+ static ASystemHealthManager* instance = nullptr;
+ std::call_once(creationFlag, []() { instance = create(nullptr); });
+ return instance;
+}
+
+ASystemHealthManager::ASystemHealthManager(std::shared_ptr<IHintManager>& hintManager)
+ : mHintManager(std::move(hintManager)) {}
+
+ASystemHealthManager::~ASystemHealthManager() {}
+
+ASystemHealthManager* ASystemHealthManager::create(std::shared_ptr<IHintManager> hintManager) {
+ if (!hintManager) {
+ hintManager = IHintManager::fromBinder(
+ ndk::SpAIBinder(AServiceManager_waitForService("performance_hint")));
+ }
+ if (hintManager == nullptr) {
+ ALOGE("%s: PerformanceHint service is not ready ", __FUNCTION__);
+ return nullptr;
+ }
+ return new ASystemHealthManager(hintManager);
+}
+
+ASystemHealthManager* ASystemHealth_acquireManager() {
+ return ASystemHealthManager::getInstance();
+}
+
+int ASystemHealthManager::getCpuHeadroom(const ACpuHeadroomParams* params, float* outHeadroom) {
+ std::optional<hal::CpuHeadroomResult> res;
+ ::ndk::ScopedAStatus ret;
+ CpuHeadroomParamsInternal internalParams;
+ if (!params) {
+ ret = mHintManager->getCpuHeadroom(internalParams, &res);
+ } else {
+ ret = mHintManager->getCpuHeadroom(*params, &res);
+ }
+ if (!ret.isOk()) {
+ LOG_ALWAYS_FATAL_IF(ret.getExceptionCode() == EX_ILLEGAL_ARGUMENT,
+ "Invalid ACpuHeadroomParams: %s", ret.getMessage());
+ ALOGE("ASystemHealth_getCpuHeadroom fails: %s", ret.getMessage());
+ if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+ return ENOTSUP;
+ } else if (ret.getExceptionCode() == EX_SECURITY) {
+ return EPERM;
+ }
+ return EPIPE;
+ }
+ *outHeadroom = res->get<hal::CpuHeadroomResult::Tag::globalHeadroom>();
+ return OK;
+}
+
+int ASystemHealthManager::getGpuHeadroom(const AGpuHeadroomParams* params, float* outHeadroom) {
+ std::optional<hal::GpuHeadroomResult> res;
+ ::ndk::ScopedAStatus ret;
+ GpuHeadroomParamsInternal internalParams;
+ if (!params) {
+ ret = mHintManager->getGpuHeadroom(internalParams, &res);
+ } else {
+ ret = mHintManager->getGpuHeadroom(*params, &res);
+ }
+ if (!ret.isOk()) {
+ LOG_ALWAYS_FATAL_IF(ret.getExceptionCode() == EX_ILLEGAL_ARGUMENT,
+ "Invalid AGpuHeadroomParams: %s", ret.getMessage());
+ ALOGE("ASystemHealth_getGpuHeadroom fails: %s", ret.getMessage());
+ if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+ return ENOTSUP;
+ }
+ return EPIPE;
+ }
+ *outHeadroom = res->get<hal::GpuHeadroomResult::Tag::globalHeadroom>();
+ return OK;
+}
+
+int ASystemHealthManager::getCpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis) {
+ int64_t minIntervalMillis = 0;
+ ::ndk::ScopedAStatus ret = mHintManager->getCpuHeadroomMinIntervalMillis(&minIntervalMillis);
+ if (!ret.isOk()) {
+ ALOGE("ASystemHealth_getCpuHeadroomMinIntervalMillis fails: %s", ret.getMessage());
+ if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+ return ENOTSUP;
+ }
+ return EPIPE;
+ }
+ *outMinIntervalMillis = minIntervalMillis;
+ return OK;
+}
+
+int ASystemHealthManager::getGpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis) {
+ int64_t minIntervalMillis = 0;
+ ::ndk::ScopedAStatus ret = mHintManager->getGpuHeadroomMinIntervalMillis(&minIntervalMillis);
+ if (!ret.isOk()) {
+ ALOGE("ASystemHealth_getGpuHeadroomMinIntervalMillis fails: %s", ret.getMessage());
+ if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+ return ENOTSUP;
+ }
+ return EPIPE;
+ }
+ *outMinIntervalMillis = minIntervalMillis;
+ return OK;
+}
+
+int ASystemHealth_getCpuHeadroom(const ACpuHeadroomParams* _Nullable params,
+ float* _Nonnull outHeadroom) {
+ LOG_ALWAYS_FATAL_IF(outHeadroom == nullptr, "%s: outHeadroom should not be null", __FUNCTION__);
+ auto manager = ASystemHealthManager::getInstance();
+ if (manager == nullptr) return ENOTSUP;
+ return manager->getCpuHeadroom(params, outHeadroom);
+}
+
+int ASystemHealth_getGpuHeadroom(const AGpuHeadroomParams* _Nullable params,
+ float* _Nonnull outHeadroom) {
+ LOG_ALWAYS_FATAL_IF(outHeadroom == nullptr, "%s: outHeadroom should not be null", __FUNCTION__);
+ auto manager = ASystemHealthManager::getInstance();
+ if (manager == nullptr) return ENOTSUP;
+ return manager->getGpuHeadroom(params, outHeadroom);
+}
+
+int ASystemHealth_getCpuHeadroomMinIntervalMillis(int64_t* _Nonnull outMinIntervalMillis) {
+ LOG_ALWAYS_FATAL_IF(outMinIntervalMillis == nullptr,
+ "%s: outMinIntervalMillis should not be null", __FUNCTION__);
+ auto manager = ASystemHealthManager::getInstance();
+ if (manager == nullptr) return ENOTSUP;
+ return manager->getCpuHeadroomMinIntervalMillis(outMinIntervalMillis);
+}
+
+int ASystemHealth_getGpuHeadroomMinIntervalMillis(int64_t* _Nonnull outMinIntervalMillis) {
+ LOG_ALWAYS_FATAL_IF(outMinIntervalMillis == nullptr,
+ "%s: outMinIntervalMillis should not be null", __FUNCTION__);
+ auto manager = ASystemHealthManager::getInstance();
+ if (manager == nullptr) return ENOTSUP;
+ return manager->getGpuHeadroomMinIntervalMillis(outMinIntervalMillis);
+}
+
+void ACpuHeadroomParams_setCalculationWindowMillis(ACpuHeadroomParams* _Nonnull params,
+ int windowMillis) {
+ LOG_ALWAYS_FATAL_IF(windowMillis < CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN ||
+ windowMillis > CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX,
+ "%s: windowMillis should be in range [50, 10000] but got %d", __FUNCTION__,
+ windowMillis);
+ params->calculationWindowMillis = windowMillis;
+}
+
+void AGpuHeadroomParams_setCalculationWindowMillis(AGpuHeadroomParams* _Nonnull params,
+ int windowMillis) {
+ LOG_ALWAYS_FATAL_IF(windowMillis < GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN ||
+ windowMillis > GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX,
+ "%s: windowMillis should be in range [50, 10000] but got %d", __FUNCTION__,
+ windowMillis);
+ params->calculationWindowMillis = windowMillis;
+}
+
+int ACpuHeadroomParams_getCalculationWindowMillis(ACpuHeadroomParams* _Nonnull params) {
+ return params->calculationWindowMillis;
+}
+
+int AGpuHeadroomParams_getCalculationWindowMillis(AGpuHeadroomParams* _Nonnull params) {
+ return params->calculationWindowMillis;
+}
+
+void ACpuHeadroomParams_setTids(ACpuHeadroomParams* _Nonnull params, const int* _Nonnull tids,
+ int tidsSize) {
+ LOG_ALWAYS_FATAL_IF(tids == nullptr, "%s: tids should not be null", __FUNCTION__);
+ LOG_ALWAYS_FATAL_IF(tidsSize > CPU_HEADROOM_MAX_TID_COUNT, "%s: tids size should not exceed 5",
+ __FUNCTION__);
+ params->tids.resize(tidsSize);
+ params->tids.clear();
+ for (int i = 0; i < tidsSize; ++i) {
+ LOG_ALWAYS_FATAL_IF(tids[i] <= 0, "ACpuHeadroomParams_setTids: Invalid non-positive tid %d",
+ tids[i]);
+ params->tids[i] = tids[i];
+ }
+}
+
+void ACpuHeadroomParams_setCalculationType(ACpuHeadroomParams* _Nonnull params,
+ ACpuHeadroomCalculationType calculationType) {
+ LOG_ALWAYS_FATAL_IF(calculationType < ACpuHeadroomCalculationType::
+ ACPU_HEADROOM_CALCULATION_TYPE_MIN ||
+ calculationType > ACpuHeadroomCalculationType::
+ ACPU_HEADROOM_CALCULATION_TYPE_AVERAGE,
+ "%s: calculationType should be one of ACpuHeadroomCalculationType values "
+ "but got %d",
+ __FUNCTION__, calculationType);
+ params->calculationType = static_cast<hal::CpuHeadroomParams::CalculationType>(calculationType);
+}
+
+ACpuHeadroomCalculationType ACpuHeadroomParams_getCalculationType(
+ ACpuHeadroomParams* _Nonnull params) {
+ return static_cast<ACpuHeadroomCalculationType>(params->calculationType);
+}
+
+void AGpuHeadroomParams_setCalculationType(AGpuHeadroomParams* _Nonnull params,
+ AGpuHeadroomCalculationType calculationType) {
+ LOG_ALWAYS_FATAL_IF(calculationType < AGpuHeadroomCalculationType::
+ AGPU_HEADROOM_CALCULATION_TYPE_MIN ||
+ calculationType > AGpuHeadroomCalculationType::
+ AGPU_HEADROOM_CALCULATION_TYPE_AVERAGE,
+ "%s: calculationType should be one of AGpuHeadroomCalculationType values "
+ "but got %d",
+ __FUNCTION__, calculationType);
+ params->calculationType = static_cast<hal::GpuHeadroomParams::CalculationType>(calculationType);
+}
+
+AGpuHeadroomCalculationType AGpuHeadroomParams_getCalculationType(
+ AGpuHeadroomParams* _Nonnull params) {
+ return static_cast<AGpuHeadroomCalculationType>(params->calculationType);
+}
+
+ACpuHeadroomParams* _Nonnull ACpuHeadroomParams_create() {
+ return new ACpuHeadroomParams();
+}
+
+AGpuHeadroomParams* _Nonnull AGpuHeadroomParams_create() {
+ return new AGpuHeadroomParams();
+}
+
+void ACpuHeadroomParams_destroy(ACpuHeadroomParams* _Nonnull params) {
+ delete params;
+}
+
+void AGpuHeadroomParams_destroy(AGpuHeadroomParams* _Nonnull params) {
+ delete params;
+}
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
index 2ba93f1..560e751 100644
--- a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
@@ -25,6 +25,7 @@
import static java.lang.annotation.RetentionPolicy.SOURCE;
+import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -91,6 +92,7 @@
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
@@ -336,20 +338,27 @@
/**
* Registers {@code observer} to listen for package failures. Add a new ObserverInternal for
* this observer if it does not already exist.
+ * For executing mitigations observers will receive callback on the given executor.
*
* <p>Observers are expected to call this on boot. It does not specify any packages but
* it will resume observing any packages requested from a previous boot.
- * @hide
+ *
+ * @param observer instance of {@link PackageHealthObserver} for observing package failures
+ * and boot loops.
+ * @param executor Executor for the thread on which observers would receive callbacks
*/
- public void registerHealthObserver(PackageHealthObserver observer) {
+ public void registerHealthObserver(@NonNull PackageHealthObserver observer,
+ @NonNull @CallbackExecutor Executor executor) {
synchronized (sLock) {
ObserverInternal internalObserver = mAllObservers.get(observer.getUniqueIdentifier());
if (internalObserver != null) {
internalObserver.registeredObserver = observer;
+ internalObserver.observerExecutor = executor;
} else {
internalObserver = new ObserverInternal(observer.getUniqueIdentifier(),
new ArrayList<>());
internalObserver.registeredObserver = observer;
+ internalObserver.observerExecutor = executor;
mAllObservers.put(observer.getUniqueIdentifier(), internalObserver);
syncState("added new observer");
}
@@ -357,40 +366,53 @@
}
/**
- * Starts observing the health of the {@code packages} for {@code observer} and notifies
- * {@code observer} of any package failures within the monitoring duration.
+ * Starts observing the health of the {@code packages} for {@code observer}.
+ * Note: Observer needs to be registered with {@link #registerHealthObserver} before calling
+ * this API.
*
* <p>If monitoring a package supporting explicit health check, at the end of the monitoring
* duration if {@link #onHealthCheckPassed} was never called,
- * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} will be called as if the package failed.
+ * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} will be called as if the
+ * package failed.
*
* <p>If {@code observer} is already monitoring a package in {@code packageNames},
* the monitoring window of that package will be reset to {@code durationMs} and the health
- * check state will be reset to a default depending on if the package is contained in
- * {@link mPackagesWithExplicitHealthCheckEnabled}.
+ * check state will be reset to a default.
*
- * <p>If {@code packageNames} is empty, this will be a no-op.
+ * <p>The {@code observer} must be registered with {@link #registerHealthObserver} before
+ * calling this method.
*
- * <p>If {@code durationMs} is less than 1, a default monitoring duration
- * {@link #DEFAULT_OBSERVING_DURATION_MS} will be used.
- * @hide
+ * @param packageNames The list of packages to check. If this is empty, the call will be a
+ * no-op.
+ *
+ * @param timeoutMs The timeout after which Explicit Health Checks would not run. If this is
+ * less than 1, a default monitoring duration 2 days will be used.
+ *
+ * @throws IllegalStateException if the observer was not previously registered
*/
- public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
- long durationMs) {
+ public void startExplicitHealthCheck(@NonNull PackageHealthObserver observer,
+ @NonNull List<String> packageNames, long timeoutMs) {
+ synchronized (sLock) {
+ if (!mAllObservers.containsKey(observer.getUniqueIdentifier())) {
+ Slog.wtf(TAG, "No observer found, need to register the observer: "
+ + observer.getUniqueIdentifier());
+ throw new IllegalStateException("Observer not registered");
+ }
+ }
if (packageNames.isEmpty()) {
Slog.wtf(TAG, "No packages to observe, " + observer.getUniqueIdentifier());
return;
}
- if (durationMs < 1) {
- Slog.wtf(TAG, "Invalid duration " + durationMs + "ms for observer "
+ if (timeoutMs < 1) {
+ Slog.wtf(TAG, "Invalid duration " + timeoutMs + "ms for observer "
+ observer.getUniqueIdentifier() + ". Not observing packages " + packageNames);
- durationMs = DEFAULT_OBSERVING_DURATION_MS;
+ timeoutMs = DEFAULT_OBSERVING_DURATION_MS;
}
List<MonitoredPackage> packages = new ArrayList<>();
for (int i = 0; i < packageNames.size(); i++) {
// Health checks not available yet so health check state will start INACTIVE
- MonitoredPackage pkg = newMonitoredPackage(packageNames.get(i), durationMs, false);
+ MonitoredPackage pkg = newMonitoredPackage(packageNames.get(i), timeoutMs, false);
if (pkg != null) {
packages.add(pkg);
} else {
@@ -423,9 +445,6 @@
}
}
- // Register observer in case not already registered
- registerHealthObserver(observer);
-
// Sync after we add the new packages to the observers. We may have received packges
// requiring an earlier schedule than we are currently scheduled for.
syncState("updated observers");
@@ -437,9 +456,8 @@
* Unregisters {@code observer} from listening to package failure.
* Additionally, this stops observing any packages that may have previously been observed
* even from a previous boot.
- * @hide
*/
- public void unregisterHealthObserver(PackageHealthObserver observer) {
+ public void unregisterHealthObserver(@NonNull PackageHealthObserver observer) {
mLongTaskHandler.post(() -> {
synchronized (sLock) {
mAllObservers.remove(observer.getUniqueIdentifier());
@@ -485,7 +503,7 @@
for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
VersionedPackage versionedPackage = packages.get(pIndex);
// Observer that will receive failure for versionedPackage
- PackageHealthObserver currentObserverToNotify = null;
+ ObserverInternal currentObserverToNotify = null;
int currentObserverImpact = Integer.MAX_VALUE;
MonitoredPackage currentMonitoredPackage = null;
@@ -506,7 +524,7 @@
versionedPackage, failureReason, mitigationCount);
if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
&& impact < currentObserverImpact) {
- currentObserverToNotify = registeredObserver;
+ currentObserverToNotify = observer;
currentObserverImpact = impact;
currentMonitoredPackage = p;
}
@@ -515,18 +533,23 @@
// Execute action with least user impact
if (currentObserverToNotify != null) {
- int mitigationCount = 1;
+ int mitigationCount;
if (currentMonitoredPackage != null) {
currentMonitoredPackage.noteMitigationCallLocked();
mitigationCount =
currentMonitoredPackage.getMitigationCountLocked();
+ } else {
+ mitigationCount = 1;
}
if (Flags.recoverabilityDetection()) {
maybeExecute(currentObserverToNotify, versionedPackage,
failureReason, currentObserverImpact, mitigationCount);
} else {
- currentObserverToNotify.onExecuteHealthCheckMitigation(
- versionedPackage, failureReason, mitigationCount);
+ PackageHealthObserver registeredObserver =
+ currentObserverToNotify.registeredObserver;
+ currentObserverToNotify.observerExecutor.execute(() ->
+ registeredObserver.onExecuteHealthCheckMitigation(
+ versionedPackage, failureReason, mitigationCount));
}
}
}
@@ -539,10 +562,11 @@
* For native crashes or explicit health check failures, call directly into each observer to
* mitigate the error without going through failure threshold logic.
*/
+ @GuardedBy("sLock")
private void handleFailureImmediately(List<VersionedPackage> packages,
@FailureReasons int failureReason) {
VersionedPackage failingPackage = packages.size() > 0 ? packages.get(0) : null;
- PackageHealthObserver currentObserverToNotify = null;
+ ObserverInternal currentObserverToNotify = null;
int currentObserverImpact = Integer.MAX_VALUE;
for (ObserverInternal observer: mAllObservers.values()) {
PackageHealthObserver registeredObserver = observer.registeredObserver;
@@ -551,7 +575,7 @@
failingPackage, failureReason, 1);
if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
&& impact < currentObserverImpact) {
- currentObserverToNotify = registeredObserver;
+ currentObserverToNotify = observer;
currentObserverImpact = impact;
}
}
@@ -561,23 +585,30 @@
maybeExecute(currentObserverToNotify, failingPackage, failureReason,
currentObserverImpact, /*mitigationCount=*/ 1);
} else {
- currentObserverToNotify.onExecuteHealthCheckMitigation(failingPackage,
- failureReason, 1);
+ PackageHealthObserver registeredObserver =
+ currentObserverToNotify.registeredObserver;
+ currentObserverToNotify.observerExecutor.execute(() ->
+ registeredObserver.onExecuteHealthCheckMitigation(failingPackage,
+ failureReason, 1));
+
}
}
}
- private void maybeExecute(PackageHealthObserver currentObserverToNotify,
+ private void maybeExecute(ObserverInternal currentObserverToNotify,
VersionedPackage versionedPackage,
@FailureReasons int failureReason,
int currentObserverImpact,
int mitigationCount) {
if (allowMitigations(currentObserverImpact, versionedPackage)) {
+ PackageHealthObserver registeredObserver;
synchronized (sLock) {
mLastMitigation = mSystemClock.uptimeMillis();
+ registeredObserver = currentObserverToNotify.registeredObserver;
}
- currentObserverToNotify.onExecuteHealthCheckMitigation(versionedPackage, failureReason,
- mitigationCount);
+ currentObserverToNotify.observerExecutor.execute(() ->
+ registeredObserver.onExecuteHealthCheckMitigation(versionedPackage,
+ failureReason, mitigationCount));
}
}
@@ -613,8 +644,7 @@
mBootThreshold.reset();
}
int mitigationCount = mBootThreshold.getMitigationCount() + 1;
- PackageHealthObserver currentObserverToNotify = null;
- ObserverInternal currentObserverInternal = null;
+ ObserverInternal currentObserverToNotify = null;
int currentObserverImpact = Integer.MAX_VALUE;
for (int i = 0; i < mAllObservers.size(); i++) {
final ObserverInternal observer = mAllObservers.valueAt(i);
@@ -626,25 +656,31 @@
: registeredObserver.onBootLoop(mitigationCount);
if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
&& impact < currentObserverImpact) {
- currentObserverToNotify = registeredObserver;
- currentObserverInternal = observer;
+ currentObserverToNotify = observer;
currentObserverImpact = impact;
}
}
}
+
if (currentObserverToNotify != null) {
+ PackageHealthObserver registeredObserver =
+ currentObserverToNotify.registeredObserver;
if (Flags.recoverabilityDetection()) {
int currentObserverMitigationCount =
- currentObserverInternal.getBootMitigationCount() + 1;
- currentObserverInternal.setBootMitigationCount(
+ currentObserverToNotify.getBootMitigationCount() + 1;
+ currentObserverToNotify.setBootMitigationCount(
currentObserverMitigationCount);
saveAllObserversBootMitigationCountToMetadata(METADATA_FILE);
- currentObserverToNotify.onExecuteBootLoopMitigation(
- currentObserverMitigationCount);
+ currentObserverToNotify.observerExecutor
+ .execute(() -> registeredObserver.onExecuteBootLoopMitigation(
+ currentObserverMitigationCount));
} else {
mBootThreshold.setMitigationCount(mitigationCount);
mBootThreshold.saveMitigationCountToMetadata();
- currentObserverToNotify.onExecuteBootLoopMitigation(mitigationCount);
+ currentObserverToNotify.observerExecutor
+ .execute(() -> registeredObserver.onExecuteBootLoopMitigation(
+ mitigationCount));
+
}
}
}
@@ -879,7 +915,7 @@
* passed to observers in these API.
*
* <p> A persistent observer may choose to start observing certain failing packages, even if
- * it has not explicitly asked to watch the package with {@link #startObservingHealth}.
+ * it has not explicitly asked to watch the package with {@link #startExplicitHealthCheck}.
*/
default boolean mayObservePackage(@NonNull String packageName) {
return false;
@@ -1136,8 +1172,10 @@
if (versionedPkg != null) {
Slog.i(TAG,
"Explicit health check failed for package " + versionedPkg);
- registeredObserver.onExecuteHealthCheckMitigation(versionedPkg,
- PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK, 1);
+ observer.observerExecutor.execute(() ->
+ registeredObserver.onExecuteHealthCheckMitigation(versionedPkg,
+ PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK,
+ 1));
}
}
}
@@ -1395,6 +1433,7 @@
@Nullable
@GuardedBy("sLock")
public PackageHealthObserver registeredObserver;
+ public Executor observerExecutor;
private int mMitigationCount;
ObserverInternal(String name, List<MonitoredPackage> packages) {
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
index 992f581..bad6ab7 100644
--- a/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
@@ -160,7 +160,7 @@
/** Register the Rescue Party observer as a Package Watchdog health observer */
public static void registerHealthObserver(Context context) {
PackageWatchdog.getInstance(context).registerHealthObserver(
- RescuePartyObserver.getInstance(context));
+ RescuePartyObserver.getInstance(context), context.getMainExecutor());
}
private static boolean isDisabled() {
@@ -313,7 +313,7 @@
callingPackageList.addAll(callingPackages);
Slog.i(TAG, "Starting to observe: " + callingPackageList + ", updated namespace: "
+ updatedNamespace);
- PackageWatchdog.getInstance(context).startObservingHealth(
+ PackageWatchdog.getInstance(context).startExplicitHealthCheck(
rescuePartyObserver,
callingPackageList,
DEFAULT_OBSERVING_DURATION_MS);
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 311def8..c80a1a4 100644
--- a/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -111,7 +111,8 @@
dataDir.mkdirs();
mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids");
mTwoPhaseRollbackEnabledFile = new File(dataDir, "two-phase-rollback-enabled");
- PackageWatchdog.getInstance(mContext).registerHealthObserver(this);
+ PackageWatchdog.getInstance(mContext).registerHealthObserver(this,
+ context.getMainExecutor());
if (SystemProperties.getBoolean("sys.boot_completed", false)) {
// Load the value from the file if system server has crashed and restarted
@@ -273,16 +274,6 @@
Preconditions.checkState(mHandler.getLooper().isCurrentThread());
}
- /**
- * Start observing health of {@code packages} for {@code durationMs}.
- * This may cause {@code packages} to be rolled back if they crash too freqeuntly.
- */
- @AnyThread
- @NonNull
- public void startObservingHealth(@NonNull List<String> packages, @NonNull long durationMs) {
- PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs);
- }
-
@AnyThread
@NonNull
public void notifyRollbackAvailable(@NonNull RollbackInfo rollback) {
diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
index 88fe36c..4fea937 100644
--- a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
@@ -87,6 +87,7 @@
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
@@ -362,7 +363,7 @@
* it will resume observing any packages requested from a previous boot.
* @hide
*/
- public void registerHealthObserver(PackageHealthObserver observer) {
+ public void registerHealthObserver(PackageHealthObserver observer, Executor ignoredExecutor) {
synchronized (mLock) {
ObserverInternal internalObserver = mAllObservers.get(observer.getUniqueIdentifier());
if (internalObserver != null) {
@@ -396,7 +397,7 @@
* {@link #DEFAULT_OBSERVING_DURATION_MS} will be used.
* @hide
*/
- public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
+ public void startExplicitHealthCheck(PackageHealthObserver observer, List<String> packageNames,
long durationMs) {
if (packageNames.isEmpty()) {
Slog.wtf(TAG, "No packages to observe, " + observer.getUniqueIdentifier());
@@ -445,7 +446,7 @@
}
// Register observer in case not already registered
- registerHealthObserver(observer);
+ registerHealthObserver(observer, null);
// Sync after we add the new packages to the observers. We may have received packges
// requiring an earlier schedule than we are currently scheduled for.
@@ -861,7 +862,7 @@
* otherwise
*
* <p> A persistent observer may choose to start observing certain failing packages, even if
- * it has not explicitly asked to watch the package with {@link #startObservingHealth}.
+ * it has not explicitly asked to watch the package with {@link #startExplicitHealthCheck}.
*/
default boolean mayObservePackage(@NonNull String packageName) {
return false;
diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java
index f757236..2bb72fb 100644
--- a/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java
@@ -166,7 +166,7 @@
/** Register the Rescue Party observer as a Package Watchdog health observer */
public static void registerHealthObserver(Context context) {
PackageWatchdog.getInstance(context).registerHealthObserver(
- RescuePartyObserver.getInstance(context));
+ RescuePartyObserver.getInstance(context), null);
}
private static boolean isDisabled() {
@@ -387,7 +387,7 @@
callingPackageList.addAll(callingPackages);
Slog.i(TAG, "Starting to observe: " + callingPackageList + ", updated namespace: "
+ updatedNamespace);
- PackageWatchdog.getInstance(context).startObservingHealth(
+ PackageWatchdog.getInstance(context).startExplicitHealthCheck(
rescuePartyObserver,
callingPackageList,
DEFAULT_OBSERVING_DURATION_MS);
diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 7445534..0692cdb 100644
--- a/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -110,7 +110,7 @@
dataDir.mkdirs();
mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids");
mTwoPhaseRollbackEnabledFile = new File(dataDir, "two-phase-rollback-enabled");
- PackageWatchdog.getInstance(mContext).registerHealthObserver(this);
+ PackageWatchdog.getInstance(mContext).registerHealthObserver(this, null);
mApexManager = apexManager;
if (SystemProperties.getBoolean("sys.boot_completed", false)) {
@@ -284,7 +284,7 @@
@AnyThread
@NonNull
public void startObservingHealth(@NonNull List<String> packages, @NonNull long durationMs) {
- PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs);
+ PackageWatchdog.getInstance(mContext).startExplicitHealthCheck(this, packages, durationMs);
}
@AnyThread
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
index cd03dd7..07b1c9e 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
@@ -22,6 +22,7 @@
import com.google.errorprone.annotations.CanIgnoreReturnValue
import java.util.WeakHashMap
import java.util.concurrent.Executor
+import java.util.concurrent.atomic.AtomicInteger
/**
* Callback to be informed of changes in [KeyedObservable] object.
@@ -203,13 +204,71 @@
}
}
- fun hasAnyObserver(): Boolean {
+ open fun hasAnyObserver(): Boolean {
synchronized(observers) { if (observers.isNotEmpty()) return true }
synchronized(keyedObservers) { if (keyedObservers.isNotEmpty()) return true }
return false
}
}
+/** [KeyedDataObservable] that maintains a counter for the observers. */
+abstract class AbstractKeyedDataObservable<K> : KeyedDataObservable<K>() {
+ /**
+ * Counter of observers.
+ *
+ * The value is accurate only when [addObserver] and [removeObserver] are invoked in pairs.
+ */
+ private val counter = AtomicInteger()
+
+ override fun addObserver(observer: KeyedObserver<K?>, executor: Executor) =
+ if (super.addObserver(observer, executor)) {
+ onObserverAdded()
+ true
+ } else {
+ false
+ }
+
+ override fun addObserver(key: K, observer: KeyedObserver<K>, executor: Executor) =
+ if (super.addObserver(key, observer, executor)) {
+ onObserverAdded()
+ true
+ } else {
+ false
+ }
+
+ private fun onObserverAdded() {
+ if (counter.getAndIncrement() == 0) onFirstObserverAdded()
+ }
+
+ /** Callbacks when the first observer is just added. */
+ protected abstract fun onFirstObserverAdded()
+
+ override fun removeObserver(observer: KeyedObserver<K?>) =
+ if (super.removeObserver(observer)) {
+ onObserverRemoved()
+ true
+ } else {
+ false
+ }
+
+ override fun removeObserver(key: K, observer: KeyedObserver<K>) =
+ if (super.removeObserver(key, observer)) {
+ onObserverRemoved()
+ true
+ } else {
+ false
+ }
+
+ private fun onObserverRemoved() {
+ if (counter.decrementAndGet() == 0) onLastObserverRemoved()
+ }
+
+ /** Callbacks when the last observer is just removed. */
+ protected abstract fun onLastObserverRemoved()
+
+ override fun hasAnyObserver() = counter.get() > 0
+}
+
/** [KeyedObservable] with no-op implementations for all interfaces. */
open class NoOpKeyedObservable<K> : KeyedObservable<K> {
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt
index 04d4bfe..d6e7a89 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt
@@ -20,21 +20,10 @@
import android.database.ContentObserver
import android.net.Uri
import android.util.Log
-import java.util.concurrent.Executor
-import java.util.concurrent.atomic.AtomicInteger
/** Base class of the Settings provider data stores. */
abstract class SettingsStore(protected val contentResolver: ContentResolver) :
- KeyedDataObservable<String>(), KeyValueStore {
-
- /**
- * Counter of observers.
- *
- * The value is accurate only when [addObserver] and [removeObserver] are called correctly. When
- * an observer is not removed (and its weak reference is garbage collected), the content
- * observer is not unregistered but this is not a big deal.
- */
- private val counter = AtomicInteger()
+ AbstractKeyedDataObservable<String>(), KeyValueStore {
private val contentObserver =
object : ContentObserver(HandlerExecutor.main) {
@@ -48,49 +37,15 @@
}
}
- override fun addObserver(observer: KeyedObserver<String?>, executor: Executor) =
- if (super.addObserver(observer, executor)) {
- onObserverAdded()
- true
- } else {
- false
- }
+ /** The URI to watch for any key change. */
+ protected abstract val uri: Uri
- override fun addObserver(key: String, observer: KeyedObserver<String>, executor: Executor) =
- if (super.addObserver(key, observer, executor)) {
- onObserverAdded()
- true
- } else {
- false
- }
-
- private fun onObserverAdded() {
- if (counter.getAndIncrement() != 0) return
+ override fun onFirstObserverAdded() {
Log.i(tag, "registerContentObserver")
contentResolver.registerContentObserver(uri, true, contentObserver)
}
- /** The URI to watch for any key change. */
- protected abstract val uri: Uri
-
- override fun removeObserver(observer: KeyedObserver<String?>) =
- if (super.removeObserver(observer)) {
- onObserverRemoved()
- true
- } else {
- false
- }
-
- override fun removeObserver(key: String, observer: KeyedObserver<String>) =
- if (super.removeObserver(key, observer)) {
- onObserverRemoved()
- true
- } else {
- false
- }
-
- private fun onObserverRemoved() {
- if (counter.decrementAndGet() != 0) return
+ override fun onLastObserverRemoved() {
Log.i(tag, "unregisterContentObserver")
contentResolver.unregisterContentObserver(contentObserver)
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
index d89d397..5a524d9 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
@@ -133,7 +133,7 @@
list.joinToString(separator = System.lineSeparator())
}
if (footer.isBlank()) return
- HorizontalDivider()
+ if (!isSpaExpressiveEnabled) HorizontalDivider()
Column(
modifier =
if (isSpaExpressiveEnabled) Modifier.padding(SettingsDimension.footerItemPadding)
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume.xml
new file mode 100644
index 0000000..2c1f300
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<level-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:maxLevel="0" android:drawable="@drawable/ic_ambient_volume_0_0" />
+ <item android:maxLevel="1" android:drawable="@drawable/ic_ambient_volume_0_1" />
+ <item android:maxLevel="2" android:drawable="@drawable/ic_ambient_volume_0_2" />
+ <item android:maxLevel="3" android:drawable="@drawable/ic_ambient_volume_0_3" />
+ <item android:maxLevel="4" android:drawable="@drawable/ic_ambient_volume_0_4" />
+ <item android:maxLevel="5" android:drawable="@drawable/ic_ambient_volume_1_0" />
+ <item android:maxLevel="6" android:drawable="@drawable/ic_ambient_volume_1_1" />
+ <item android:maxLevel="7" android:drawable="@drawable/ic_ambient_volume_1_2" />
+ <item android:maxLevel="8" android:drawable="@drawable/ic_ambient_volume_1_3" />
+ <item android:maxLevel="9" android:drawable="@drawable/ic_ambient_volume_1_4" />
+ <item android:maxLevel="10" android:drawable="@drawable/ic_ambient_volume_2_0" />
+ <item android:maxLevel="11" android:drawable="@drawable/ic_ambient_volume_2_1" />
+ <item android:maxLevel="12" android:drawable="@drawable/ic_ambient_volume_2_2" />
+ <item android:maxLevel="13" android:drawable="@drawable/ic_ambient_volume_2_3" />
+ <item android:maxLevel="14" android:drawable="@drawable/ic_ambient_volume_2_4" />
+ <item android:maxLevel="15" android:drawable="@drawable/ic_ambient_volume_3_0" />
+ <item android:maxLevel="16" android:drawable="@drawable/ic_ambient_volume_3_1" />
+ <item android:maxLevel="17" android:drawable="@drawable/ic_ambient_volume_3_2" />
+ <item android:maxLevel="18" android:drawable="@drawable/ic_ambient_volume_3_3" />
+ <item android:maxLevel="19" android:drawable="@drawable/ic_ambient_volume_3_4" />
+ <item android:maxLevel="20" android:drawable="@drawable/ic_ambient_volume_4_0" />
+ <item android:maxLevel="21" android:drawable="@drawable/ic_ambient_volume_4_1" />
+ <item android:maxLevel="22" android:drawable="@drawable/ic_ambient_volume_4_2" />
+ <item android:maxLevel="23" android:drawable="@drawable/ic_ambient_volume_4_3" />
+ <item android:maxLevel="24" android:drawable="@drawable/ic_ambient_volume_4_4" />
+</level-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_0_0.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_0.xml
new file mode 100644
index 0000000..738fe89
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_0.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M792,904L671,783Q646,799 618,810.5Q590,822 560,829L560,747Q574,742 587.5,737Q601,732 613,725L480,592L480,800L280,600L120,600L120,360L248,360L56,168L112,112L848,848L792,904ZM784,672L726,614Q743,583 751.5,549Q760,515 760,479Q760,385 705,311Q650,237 560,211L560,129Q684,157 762,254.5Q840,352 840,479Q840,532 825.5,581Q811,630 784,672ZM650,538L560,448L560,318Q607,340 633.5,384Q660,428 660,480Q660,495 657.5,509.5Q655,524 650,538ZM480,368L376,264L480,160L480,368ZM400,606L400,512L328,440L328,440L200,440L200,520L314,520L400,606ZM364,476L364,476L364,476L364,476L364,476L364,476L364,476Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_0_1.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_1.xml
new file mode 100644
index 0000000..8de24f9
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_1.xml
@@ -0,0 +1,27 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_0_2.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_2.xml
new file mode 100644
index 0000000..267093e
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_2.xml
@@ -0,0 +1,31 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_0_3.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_3.xml
new file mode 100644
index 0000000..fb21254
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_3.xml
@@ -0,0 +1,35 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_0_4.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_4.xml
new file mode 100644
index 0000000..4f1e054
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_4.xml
@@ -0,0 +1,39 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.917,0.761L12.146,0.286L11.073,4.192L12.844,4.666L13.917,0.761Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.002,3.661L16.706,2.365L13.86,5.211L15.157,6.507L18.002,3.661Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M20.123,8.938L19.868,7.123L15.858,7.632L16.113,9.447L20.123,8.938Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M19.443,13.831L19.979,12.078L16.103,10.898L15.567,12.651L19.443,13.831Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_1_0.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_0.xml
new file mode 100644
index 0000000..d151fee
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_0.xml
@@ -0,0 +1,27 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_1_1.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_1.xml
new file mode 100644
index 0000000..3e97a2e
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_1.xml
@@ -0,0 +1,31 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_1_2.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_2.xml
new file mode 100644
index 0000000..7bfd662
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_2.xml
@@ -0,0 +1,35 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_1_3.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_3.xml
new file mode 100644
index 0000000..bba33b3
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_3.xml
@@ -0,0 +1,39 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_1_4.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_4.xml
new file mode 100644
index 0000000..c004316
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_4.xml
@@ -0,0 +1,43 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.917,0.761L12.146,0.286L11.073,4.192L12.844,4.666L13.917,0.761Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.002,3.661L16.706,2.365L13.86,5.211L15.157,6.507L18.002,3.661Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M20.123,8.938L19.868,7.123L15.858,7.632L16.113,9.447L20.123,8.938Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M19.443,13.831L19.979,12.078L16.103,10.898L15.567,12.651L19.443,13.831Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_2_0.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_0.xml
new file mode 100644
index 0000000..d89ed87
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_0.xml
@@ -0,0 +1,31 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_2_1.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_1.xml
new file mode 100644
index 0000000..e0a9c41
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_1.xml
@@ -0,0 +1,35 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_2_2.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_2.xml
new file mode 100644
index 0000000..8627af9
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_2.xml
@@ -0,0 +1,39 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_2_3.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_3.xml
new file mode 100644
index 0000000..2c1139a
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_3.xml
@@ -0,0 +1,43 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_2_4.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_4.xml
new file mode 100644
index 0000000..8d920b1
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_4.xml
@@ -0,0 +1,47 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.917,0.761L12.146,0.286L11.073,4.192L12.844,4.666L13.917,0.761Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.002,3.661L16.706,2.365L13.86,5.211L15.157,6.507L18.002,3.661Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M20.123,8.938L19.868,7.123L15.858,7.632L16.113,9.447L20.123,8.938Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M19.443,13.831L19.979,12.078L16.103,10.898L15.567,12.651L19.443,13.831Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_3_0.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_0.xml
new file mode 100644
index 0000000..7fd4e17
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_0.xml
@@ -0,0 +1,39 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.043,0.761L8.814,0.286L9.887,4.192L8.116,4.666L7.043,0.761Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.402,2.08L9.173,1.606L9.887,4.187L8.116,4.661L7.402,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.02,3.652L4.317,2.355L7.162,5.201L5.866,6.497L3.02,3.652Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.985,4.621L5.281,3.324L7.163,5.21L5.866,6.507L3.985,4.621Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M0.898,8.942L1.153,7.127L5.163,7.636L4.908,9.451L0.898,8.942Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.251,9.109L2.506,7.293L5.163,7.626L4.908,9.442L2.251,9.109Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M1.589,13.874L1.053,12.121L4.93,10.941L5.466,12.694L1.589,13.874Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.933,13.463L2.397,11.71L4.93,10.948L5.466,12.702L2.933,13.463Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.936L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_3_1.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_1.xml
new file mode 100644
index 0000000..f0f8150
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_1.xml
@@ -0,0 +1,39 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.402,2.08L9.173,1.606L9.887,4.187L8.116,4.661L7.402,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.985,4.621L5.281,3.324L7.163,5.21L5.866,6.507L3.985,4.621Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.251,9.109L2.506,7.293L5.163,7.626L4.908,9.442L2.251,9.109Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.381L4.666,7.566L5.162,7.627L4.907,9.459L4.411,9.381Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.933,13.463L2.397,11.71L4.93,10.948L5.466,12.702L2.933,13.463Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.936L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_3_2.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_2.xml
new file mode 100644
index 0000000..1bb2017
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_2.xml
@@ -0,0 +1,43 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.652L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.402,2.08L9.173,1.606L9.887,4.187L8.116,4.661L7.402,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.985,4.621L5.281,3.324L7.163,5.21L5.866,6.507L3.985,4.621Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.251,9.109L2.506,7.293L5.163,7.626L4.908,9.442L2.251,9.109Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.381L4.666,7.566L5.162,7.627L4.907,9.459L4.411,9.381Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.933,13.463L2.397,11.71L4.93,10.948L5.466,12.702L2.933,13.463Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.936L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_3_3.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_3.xml
new file mode 100644
index 0000000..a8bc0af
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_3.xml
@@ -0,0 +1,47 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.652L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.402,2.08L9.173,1.606L9.887,4.187L8.116,4.661L7.402,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.985,4.621L5.281,3.324L7.163,5.21L5.866,6.507L3.985,4.621Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.251,9.109L2.506,7.293L5.163,7.626L4.908,9.442L2.251,9.109Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.381L4.666,7.566L5.162,7.627L4.907,9.459L4.411,9.381Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.933,13.463L2.397,11.71L4.93,10.948L5.466,12.702L2.933,13.463Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.936L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_3_4.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_4.xml
new file mode 100644
index 0000000..f8a5832
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_4.xml
@@ -0,0 +1,51 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.917,0.761L12.146,0.286L11.073,4.192L12.844,4.666L13.917,0.761Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.002,3.661L16.706,2.365L13.86,5.211L15.157,6.507L18.002,3.661Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M20.123,8.938L19.868,7.123L15.858,7.632L16.113,9.447L20.123,8.938Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M19.443,13.831L19.979,12.078L16.103,10.898L15.567,12.651L19.443,13.831Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.652L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.402,2.08L9.173,1.606L9.887,4.187L8.116,4.661L7.402,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.985,4.621L5.281,3.324L7.162,5.21L5.866,6.507L3.985,4.621Z"/>
+ <path android:fillAlpha="0.4" android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.251,9.109L2.506,7.293L5.163,7.626L4.908,9.442L2.251,9.109Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.381L4.666,7.566L5.162,7.627L4.907,9.459L4.411,9.381Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.933,13.463L2.397,11.71L4.93,10.948L5.466,12.702L2.933,13.463Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.936L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_4_0.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_0.xml
new file mode 100644
index 0000000..e5d7ad4
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_0.xml
@@ -0,0 +1,27 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M6.983,0.765L8.754,0.29L9.947,4.192L8.177,4.666L6.983,0.765Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.974,3.583L4.271,2.287L7.16,5.211L5.864,6.507L2.974,3.583Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M0.879,8.868L1.134,7.052L5.163,7.632L4.908,9.447L0.879,8.868Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M1.601,14.021L1.065,12.268L4.918,10.898L5.454,12.651L1.601,14.021Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_4_1.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_1.xml
new file mode 100644
index 0000000..f5cdf5d
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_1.xml
@@ -0,0 +1,31 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M6.983,0.765L8.754,0.29L9.947,4.192L8.177,4.666L6.983,0.765Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.974,3.583L4.271,2.287L7.16,5.211L5.864,6.507L2.974,3.583Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M0.879,8.868L1.134,7.052L5.163,7.632L4.908,9.447L0.879,8.868Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M1.601,14.021L1.065,12.268L4.918,10.898L5.454,12.651L1.601,14.021Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_4_2.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_2.xml
new file mode 100644
index 0000000..cbed634
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_2.xml
@@ -0,0 +1,35 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M6.983,0.765L8.754,0.29L9.947,4.192L8.177,4.666L6.983,0.765Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.974,3.583L4.271,2.287L7.16,5.211L5.864,6.507L2.974,3.583Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M0.879,8.868L1.134,7.052L5.163,7.632L4.908,9.447L0.879,8.868Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M1.601,14.021L1.065,12.268L4.918,10.898L5.454,12.651L1.601,14.021Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_4_3.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_3.xml
new file mode 100644
index 0000000..90d81d8
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_3.xml
@@ -0,0 +1,39 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M6.983,0.765L8.754,0.29L9.947,4.192L8.177,4.666L6.983,0.765Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.974,3.583L4.271,2.287L7.16,5.211L5.864,6.507L2.974,3.583Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M0.879,8.868L1.134,7.052L5.163,7.632L4.908,9.447L0.879,8.868Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M1.601,14.021L1.065,12.268L4.918,10.898L5.454,12.651L1.601,14.021Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.566,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_4_4.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_4.xml
new file mode 100644
index 0000000..f1a9a8a
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_4.xml
@@ -0,0 +1,43 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M6.983,0.765L8.754,0.29L9.947,4.192L8.177,4.666L6.983,0.765Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.975,3.583L4.271,2.287L7.16,5.211L5.864,6.507L2.975,3.583Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M0.878,8.868L1.134,7.052L5.163,7.632L4.907,9.447L0.878,8.868Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M1.601,14.021L1.065,12.268L4.918,10.898L5.454,12.651L1.601,14.021Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.917,0.761L12.146,0.286L11.073,4.192L12.844,4.666L13.917,0.761Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.002,3.661L16.706,2.365L13.86,5.211L15.157,6.507L18.002,3.661Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M20.123,8.938L19.868,7.123L15.858,7.632L16.113,9.447L20.123,8.938Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M19.443,13.831L19.979,12.078L16.103,10.898L15.567,12.651L19.443,13.831Z"/>
+ <path android:fillAlpha="0.4" android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z" android:strokeAlpha="0.4"/>
+ <path android:fillAlpha="0.4" android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z" android:strokeAlpha="0.4"/>
+ <path android:fillAlpha="0.4" android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z" android:strokeAlpha="0.4"/>
+ <path android:fillAlpha="0.4" android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z" android:strokeAlpha="0.4"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+</vector>
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
index 35e3dd3..e1be1d2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
@@ -231,7 +231,7 @@
public SignalStrength signalStrength;
public TelephonyDisplayInfo telephonyDisplayInfo =
new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
- TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false);
+ TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false, false, false);
/**
* Empty constructor
diff --git a/packages/Shell/Android.bp b/packages/Shell/Android.bp
index 5f81085..5fdf045 100644
--- a/packages/Shell/Android.bp
+++ b/packages/Shell/Android.bp
@@ -12,7 +12,10 @@
"src/**/*.java",
":dumpstate_aidl",
]
-shell_static_libs = ["androidx.legacy_legacy-support-v4"]
+shell_static_libs = [
+ "androidx.legacy_legacy-support-v4",
+ "wear_aconfig_declarations_flags_java_lib",
+]
android_app {
name: "Shell",
@@ -28,6 +31,7 @@
flags_packages: [
"android.security.flags-aconfig",
"android.permission.flags-aconfig",
+ "wear_aconfig_declarations",
],
platform_apis: true,
certificate: "platform",
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index fa6e2db..baf829a 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -740,6 +740,9 @@
<uses-permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE" />
<uses-permission android:name="android.permission.DEBUG_VIRTUAL_MACHINE" />
+ <!-- Permission required to access bugreport and screenshot files created by wear. -->
+ <uses-permission android:name="com.google.wear.permission.ACCESS_BUG_REPORT_FILES" />
+
<!-- Permission required to run GtsAssistantTestCases -->
<uses-permission android:name="android.permission.MANAGE_VOICE_KEYPHRASES" />
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 7f25b51..9736831 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -24,6 +24,7 @@
import static com.android.shell.BugreportPrefs.STATE_HIDE;
import static com.android.shell.BugreportPrefs.STATE_UNKNOWN;
import static com.android.shell.BugreportPrefs.getWarningState;
+import static com.android.shell.flags.Flags.handleBugreportsForWear;
import android.accounts.Account;
import android.accounts.AccountManager;
@@ -89,10 +90,10 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.google.android.collect.Lists;
-
import libcore.io.Streams;
+import com.google.android.collect.Lists;
+
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
@@ -140,6 +141,7 @@
public class BugreportProgressService extends Service {
private static final String TAG = "BugreportProgressService";
private static final boolean DEBUG = false;
+ private static final String WRITE_AND_APPEND_MODE = "wa";
private Intent startSelfIntent;
@@ -384,7 +386,11 @@
}
private static String getFileName(BugreportInfo info, String suffix) {
- return String.format("%s-%s%s", info.baseName, info.getName(), suffix);
+ return getFileName(suffix, info.baseName, info.getName());
+ }
+
+ private static String getFileName(String suffix, String baseName, String name) {
+ return String.format("%s-%s%s", baseName, name, suffix);
}
private final class BugreportCallbackImpl extends BugreportCallback {
@@ -420,14 +426,14 @@
@Override
public void onFinished() {
- mInfo.renameBugreportFile();
- mInfo.renameScreenshots();
- if (mInfo.bugreportFile.length() == 0) {
- Log.e(TAG, "Bugreport file empty. File path = " + mInfo.bugreportFile);
- onError(BUGREPORT_ERROR_RUNTIME);
- return;
- }
synchronized (mLock) {
+ mInfo.renameBugreportFile();
+ mInfo.renameScreenshots();
+ if (mInfo.bugreportLocationInfo.isFileEmpty(mContext)) {
+ Log.e(TAG, "Bugreport file empty. File path = " + mInfo.bugreportLocationInfo);
+ onError(BUGREPORT_ERROR_RUNTIME);
+ return;
+ }
sendBugreportFinishedBroadcastLocked();
mMainThreadHandler.post(() -> mInfoDialog.onBugreportFinished(mInfo));
}
@@ -454,15 +460,15 @@
@GuardedBy("mLock")
private void sendBugreportFinishedBroadcastLocked() {
- final String bugreportFilePath = mInfo.bugreportFile.getAbsolutePath();
- if (mInfo.type == BugreportParams.BUGREPORT_MODE_REMOTE) {
- sendRemoteBugreportFinishedBroadcast(mContext, bugreportFilePath,
- mInfo.bugreportFile, mInfo.nonce);
+ File bugreportFile = mInfo.bugreportLocationInfo.mBugreportFile;
+ if (mInfo.type == BugreportParams.BUGREPORT_MODE_REMOTE && bugreportFile != null) {
+ sendRemoteBugreportFinishedBroadcast(
+ mContext, bugreportFile.getAbsolutePath(), bugreportFile, mInfo.nonce);
} else {
cleanupOldFiles(MIN_KEEP_COUNT, MIN_KEEP_AGE, mBugreportsDir);
final Intent intent = new Intent(INTENT_BUGREPORT_FINISHED);
- intent.putExtra(EXTRA_BUGREPORT, bugreportFilePath);
- intent.putExtra(EXTRA_SCREENSHOT, getScreenshotForIntent(mInfo));
+ intent.putExtra(EXTRA_BUGREPORT, mInfo.bugreportLocationInfo.getBugreportPath());
+ intent.putExtra(EXTRA_SCREENSHOT, mInfo.screenshotLocationInfo.getScreenshotPath());
mContext.sendBroadcast(intent, android.Manifest.permission.DUMP);
onBugreportFinished(mInfo);
}
@@ -498,19 +504,6 @@
android.Manifest.permission.DUMP);
}
- /**
- * Checks if screenshot array is non-empty and returns the first screenshot's path. The first
- * screenshot is the default screenshot for the bugreport types that take it.
- */
- private static String getScreenshotForIntent(BugreportInfo info) {
- if (!info.screenshotFiles.isEmpty()) {
- final File screenshotFile = info.screenshotFiles.get(0);
- final String screenshotFilePath = screenshotFile.getAbsolutePath();
- return screenshotFilePath;
- }
- return null;
- }
-
private static String generateFileHash(String fileName) {
String fileHash = null;
try {
@@ -715,24 +708,28 @@
String name = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date());
List<Uri> extraAttachments =
intent.getParcelableArrayListExtra(EXTRA_EXTRA_ATTACHMENT_URIS, Uri.class);
-
- BugreportInfo info = new BugreportInfo(mContext, baseName, name, shareTitle,
- shareDescription, bugreportType, mBugreportsDir, nonce, extraAttachments);
- synchronized (mLock) {
- if (info.bugreportFile.exists()) {
- Log.e(TAG, "Failed to start bugreport generation, the requested bugreport file "
- + info.bugreportFile + " already exists");
- return;
- }
- info.createBugreportFile();
+ BugreportInfo info =
+ setupFilesAndCreateBugreportInfo(
+ intent,
+ bugreportType,
+ baseName,
+ name,
+ shareTitle,
+ shareDescription,
+ nonce,
+ extraAttachments);
+ if (info == null) {
+ Log.e(TAG, "Could not initialize bugreport inputs");
+ return;
}
+
ParcelFileDescriptor bugreportFd = info.getBugreportFd();
if (bugreportFd == null) {
Log.e(TAG, "Failed to start bugreport generation as "
+ " bugreport parcel file descriptor is null.");
return;
}
- info.createScreenshotFile(mBugreportsDir);
+
ParcelFileDescriptor screenshotFd = null;
if (isDefaultScreenshotRequired(bugreportType, /* hasScreenshotButton= */ !mIsTv)) {
screenshotFd = info.getDefaultScreenshotFd();
@@ -740,7 +737,7 @@
Log.e(TAG, "Failed to start bugreport generation as"
+ " screenshot parcel file descriptor is null. Deleting bugreport file");
FileUtils.closeQuietly(bugreportFd);
- info.bugreportFile.delete();
+ info.bugreportLocationInfo.maybeDeleteBugreportFile();
return;
}
}
@@ -768,6 +765,56 @@
}
}
+ // Sets up BugreportInfo. If needed, creates bugreport and screenshot files.
+ private BugreportInfo setupFilesAndCreateBugreportInfo(
+ Intent intent,
+ int bugreportType,
+ String baseName,
+ String name,
+ String shareTitle,
+ String shareDescription,
+ long nonce,
+ List<Uri> extraAttachments) {
+ ArrayList<Uri> brAndScreenshot;
+ Uri bugReportUri = null;
+ Uri screenshotUri = null;
+
+ if (handleBugreportsForWear() && bugreportType == BugreportParams.BUGREPORT_MODE_WEAR) {
+ brAndScreenshot = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
+ if (brAndScreenshot != null && !brAndScreenshot.isEmpty()) {
+ bugReportUri = brAndScreenshot.get(0);
+ if (bugReportUri == null) {
+ Log.e(TAG, "Can't start bugreport request. Bugreport uri is null.");
+ return null;
+ }
+ screenshotUri = (brAndScreenshot.size() > 1) ? brAndScreenshot.get(1) : null;
+ }
+ }
+
+ BugreportLocationInfo bugreportLocationInfo =
+ new BugreportLocationInfo(bugReportUri, mBugreportsDir, baseName, name);
+ ScreenshotLocationInfo screenshotLocationInfo = new ScreenshotLocationInfo(screenshotUri);
+ BugreportInfo info =
+ new BugreportInfo(
+ mContext,
+ baseName,
+ name,
+ shareTitle,
+ shareDescription,
+ bugreportType,
+ nonce,
+ extraAttachments,
+ bugreportLocationInfo,
+ screenshotLocationInfo);
+ synchronized (mLock) {
+ if (!bugreportLocationInfo.maybeCreateBugreportFile()) {
+ return null;
+ }
+ }
+ info.maybeCreateScreenshotFile(mBugreportsDir);
+ return info;
+ }
+
private static boolean isDefaultScreenshotRequired(
@BugreportParams.BugreportMode int bugreportType,
boolean hasScreenshotButton) {
@@ -1177,8 +1224,9 @@
stopForegroundWhenDoneLocked(info.id);
}
- if (!info.bugreportFile.exists() || !info.bugreportFile.canRead()) {
- Log.e(TAG, "Could not read bugreport file " + info.bugreportFile);
+ File bugreportFile = info.bugreportLocationInfo.mBugreportFile;
+ if (!info.bugreportLocationInfo.isValidBugreportResult()) {
+ Log.e(TAG, "Could not read bugreport file " + bugreportFile);
Toast.makeText(mContext, R.string.bugreport_unreadable_text, Toast.LENGTH_LONG).show();
synchronized (mLock) {
stopProgressLocked(info.id);
@@ -1194,7 +1242,7 @@
* the bugreport.
*/
private void triggerLocalNotification(final BugreportInfo info) {
- boolean isPlainText = info.bugreportFile.getName().toLowerCase().endsWith(".txt");
+ boolean isPlainText = info.bugreportLocationInfo.isPlainText();
if (!isPlainText) {
// Already zipped, send it right away.
sendBugreportNotification(info, mTakingScreenshot);
@@ -1223,11 +1271,11 @@
// grant temporary permissions for.
final Uri bugreportUri;
try {
- bugreportUri = getUri(context, info.bugreportFile);
+ bugreportUri = getUri(context, info.bugreportLocationInfo.mBugreportFile);
} catch (IllegalArgumentException e) {
// Should not happen on production, but happens when a Shell is sideloaded and
// FileProvider cannot find a configured root for it.
- Log.wtf(TAG, "Could not get URI for " + info.bugreportFile, e);
+ Log.wtf(TAG, "Could not get URI for " + info.bugreportLocationInfo.mBugreportFile, e);
return null;
}
@@ -1258,7 +1306,7 @@
new ClipData.Item(null, null, null, bugreportUri));
Log.d(TAG, "share intent: bureportUri=" + bugreportUri);
final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri);
- for (File screenshot : info.screenshotFiles) {
+ for (File screenshot : info.screenshotLocationInfo.mScreenshotFiles) {
final Uri screenshotUri = getUri(context, screenshot);
Log.d(TAG, "share intent: screenshotUri=" + screenshotUri);
clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
@@ -1512,22 +1560,25 @@
* original in case of failure).
*/
private static void zipBugreport(BugreportInfo info) {
- final String bugreportPath = info.bugreportFile.getAbsolutePath();
+ File bugreportFile = info.bugreportLocationInfo.mBugreportFile;
+ final String bugreportPath = bugreportFile.getAbsolutePath();
final String zippedPath = bugreportPath.replace(".txt", ".zip");
Log.v(TAG, "zipping " + bugreportPath + " as " + zippedPath);
final File bugreportZippedFile = new File(zippedPath);
- try (InputStream is = new FileInputStream(info.bugreportFile);
- ZipOutputStream zos = new ZipOutputStream(
- new BufferedOutputStream(new FileOutputStream(bugreportZippedFile)))) {
- addEntry(zos, info.bugreportFile.getName(), is);
+ try (InputStream is = new FileInputStream(bugreportFile);
+ ZipOutputStream zos =
+ new ZipOutputStream(
+ new BufferedOutputStream(
+ new FileOutputStream(bugreportZippedFile)))) {
+ addEntry(zos, bugreportFile.getName(), is);
// Delete old file
- final boolean deleted = info.bugreportFile.delete();
+ final boolean deleted = bugreportFile.delete();
if (deleted) {
Log.v(TAG, "deleted original bugreport (" + bugreportPath + ")");
} else {
Log.e(TAG, "could not delete original bugreport (" + bugreportPath + ")");
}
- info.bugreportFile = bugreportZippedFile;
+ info.bugreportLocationInfo.mBugreportFile = bugreportZippedFile;
} catch (IOException e) {
Log.e(TAG, "exception zipping file " + zippedPath, e);
}
@@ -1557,7 +1608,11 @@
@GuardedBy("mLock")
private void addDetailsToZipFileLocked(BugreportInfo info) {
- if (info.bugreportFile == null) {
+ if (handleBugreportsForWear()) {
+ Log.d(TAG, "Skipping adding details to zipped file");
+ return;
+ }
+ if (info.bugreportLocationInfo.mBugreportFile == null) {
// One possible reason is a bug in the Parcelization code.
Log.wtf(TAG, "addDetailsToZipFile(): no bugreportFile on " + info);
return;
@@ -1588,10 +1643,11 @@
sendBugreportBeingUpdatedNotification(mContext, info.id); // ...and that takes time
}
- final File dir = info.bugreportFile.getParentFile();
- final File tmpZip = new File(dir, "tmp-" + info.bugreportFile.getName());
+ File bugreportFile = info.bugreportLocationInfo.mBugreportFile;
+ final File dir = bugreportFile.getParentFile();
+ final File tmpZip = new File(dir, "tmp-" + bugreportFile.getName());
Log.d(TAG, "Writing temporary zip file (" + tmpZip + ") with title and/or description");
- try (ZipFile oldZip = new ZipFile(info.bugreportFile);
+ try (ZipFile oldZip = new ZipFile(bugreportFile);
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(tmpZip))) {
// First copy contents from original zip.
@@ -1628,8 +1684,8 @@
stopForegroundWhenDoneLocked(info.id);
}
- if (!tmpZip.renameTo(info.bugreportFile)) {
- Log.e(TAG, "Could not rename " + tmpZip + " to " + info.bugreportFile);
+ if (!tmpZip.renameTo(bugreportFile)) {
+ Log.e(TAG, "Could not rename " + tmpZip + " to " + bugreportFile);
}
}
@@ -2087,15 +2143,9 @@
*/
String formattedLastUpdate;
- /**
- * Path of the main bugreport file.
- */
- File bugreportFile;
+ BugreportLocationInfo bugreportLocationInfo;
- /**
- * Path of the screenshot files.
- */
- List<File> screenshotFiles = new ArrayList<>(1);
+ ScreenshotLocationInfo screenshotLocationInfo;
/**
* Whether dumpstate sent an intent informing it has finished.
@@ -2138,10 +2188,17 @@
/**
* Constructor for tracked bugreports - typically called upon receiving BUGREPORT_REQUESTED.
*/
- BugreportInfo(Context context, String baseName, String name,
- @Nullable String shareTitle, @Nullable String shareDescription,
- @BugreportParams.BugreportMode int type, File bugreportsDir, long nonce,
- @Nullable List<Uri> extraAttachments) {
+ BugreportInfo(
+ Context context,
+ String baseName,
+ String name,
+ @Nullable String shareTitle,
+ @Nullable String shareDescription,
+ @BugreportParams.BugreportMode int type,
+ long nonce,
+ @Nullable List<Uri> extraAttachments,
+ BugreportLocationInfo bugreportLocationInfo,
+ ScreenshotLocationInfo screenshotLocationInfo) {
this.context = context;
this.name = this.initialName = name;
this.shareTitle = shareTitle == null ? "" : shareTitle;
@@ -2149,29 +2206,27 @@
this.type = type;
this.nonce = nonce;
this.baseName = baseName;
- this.bugreportFile = new File(bugreportsDir, getFileName(this, ".zip"));
+ this.bugreportLocationInfo = bugreportLocationInfo;
+ this.screenshotLocationInfo = screenshotLocationInfo;
this.extraAttachments = extraAttachments;
}
- void createBugreportFile() {
- createReadWriteFile(bugreportFile);
- }
-
- void createScreenshotFile(File bugreportsDir) {
+ void maybeCreateScreenshotFile(File bugreportsDir) {
+ if (screenshotLocationInfo.mScreenshotUri != null) {
+ // Screenshot file was already created.
+ return;
+ }
File screenshotFile = new File(bugreportsDir, getScreenshotName("default"));
addScreenshot(screenshotFile);
createReadWriteFile(screenshotFile);
}
ParcelFileDescriptor getBugreportFd() {
- return getFd(bugreportFile);
+ return bugreportLocationInfo.getBugreportFd(context);
}
ParcelFileDescriptor getDefaultScreenshotFd() {
- if (screenshotFiles.isEmpty()) {
- return null;
- }
- return getFd(screenshotFiles.get(0));
+ return screenshotLocationInfo.getScreenshotFd(context);
}
void setTitle(String title) {
@@ -2229,14 +2284,14 @@
* Saves the location of a taken screenshot so it can be sent out at the end.
*/
void addScreenshot(File screenshot) {
- screenshotFiles.add(screenshot);
+ screenshotLocationInfo.mScreenshotFiles.add(screenshot);
}
/**
* Deletes all screenshots taken for a given bugreport.
*/
private void deleteScreenshots() {
- for (File file : screenshotFiles) {
+ for (File file : screenshotLocationInfo.mScreenshotFiles) {
Log.i(TAG, "Deleting screenshot file " + file);
file.delete();
}
@@ -2246,18 +2301,14 @@
* Deletes bugreport file for a given bugreport.
*/
private void deleteBugreportFile() {
- Log.i(TAG, "Deleting bugreport file " + bugreportFile);
- bugreportFile.delete();
+ bugreportLocationInfo.maybeDeleteBugreportFile();
}
/**
* Deletes empty files for a given bugreport.
*/
private void deleteEmptyFiles() {
- if (bugreportFile.length() == 0) {
- Log.i(TAG, "Deleting empty bugreport file: " + bugreportFile);
- bugreportFile.delete();
- }
+ bugreportLocationInfo.maybeDeleteEmptyBugreport();
deleteEmptyScreenshots();
}
@@ -2265,14 +2316,7 @@
* Deletes empty screenshot files.
*/
private void deleteEmptyScreenshots() {
- screenshotFiles.removeIf(file -> {
- final long length = file.length();
- if (length == 0) {
- Log.i(TAG, "Deleting empty screenshot file: " + file);
- file.delete();
- }
- return length == 0;
- });
+ screenshotLocationInfo.deleteEmptyScreenshots();
}
/**
@@ -2280,43 +2324,14 @@
* {@code initialName} if user has changed it.
*/
void renameScreenshots() {
- deleteEmptyScreenshots();
- if (TextUtils.isEmpty(name) || screenshotFiles.isEmpty()) {
- return;
- }
- final List<File> renamedFiles = new ArrayList<>(screenshotFiles.size());
- for (File oldFile : screenshotFiles) {
- final String oldName = oldFile.getName();
- final String newName = oldName.replaceFirst(initialName, name);
- final File newFile;
- if (!newName.equals(oldName)) {
- final File renamedFile = new File(oldFile.getParentFile(), newName);
- Log.d(TAG, "Renaming screenshot file " + oldFile + " to " + renamedFile);
- newFile = oldFile.renameTo(renamedFile) ? renamedFile : oldFile;
- } else {
- Log.w(TAG, "Name didn't change: " + oldName);
- newFile = oldFile;
- }
- if (newFile.length() > 0) {
- renamedFiles.add(newFile);
- } else if (newFile.delete()) {
- Log.d(TAG, "screenshot file: " + newFile + " deleted successfully.");
- }
- }
- screenshotFiles = renamedFiles;
+ screenshotLocationInfo.renameScreenshots(initialName, name);
}
/**
* Rename bugreport file to include the name given by user via UI
*/
void renameBugreportFile() {
- File newBugreportFile = new File(bugreportFile.getParentFile(),
- getFileName(this, ".zip"));
- if (!newBugreportFile.getPath().equals(bugreportFile.getPath())) {
- if (bugreportFile.renameTo(newBugreportFile)) {
- bugreportFile = newBugreportFile;
- }
- }
+ bugreportLocationInfo.maybeRenameBugreportFile(this);
}
String getFormattedLastUpdate() {
@@ -2349,16 +2364,23 @@
builder.append("(").append(description.length()).append(" chars)");
}
- return builder
- .append("\n\tfile: ").append(bugreportFile)
- .append("\n\tscreenshots: ").append(screenshotFiles)
- .append("\n\tprogress: ").append(progress)
- .append("\n\tlast_update: ").append(getFormattedLastUpdate())
- .append("\n\taddingDetailsToZip: ").append(addingDetailsToZip)
- .append(" addedDetailsToZip: ").append(addedDetailsToZip)
- .append("\n\tshareDescription: ").append(shareDescription)
- .append("\n\tshareTitle: ").append(shareTitle)
- .toString();
+ return builder.append("\n\tfile: ")
+ .append(bugreportLocationInfo)
+ .append("\n\tscreenshots: ")
+ .append(screenshotLocationInfo)
+ .append("\n\tprogress: ")
+ .append(progress)
+ .append("\n\tlast_update: ")
+ .append(getFormattedLastUpdate())
+ .append("\n\taddingDetailsToZip: ")
+ .append(addingDetailsToZip)
+ .append(" addedDetailsToZip: ")
+ .append(addedDetailsToZip)
+ .append("\n\tshareDescription: ")
+ .append(shareDescription)
+ .append("\n\tshareTitle: ")
+ .append(shareTitle)
+ .toString();
}
// Parcelable contract
@@ -2375,11 +2397,12 @@
lastProgress.set(in.readInt());
lastUpdate.set(in.readLong());
formattedLastUpdate = in.readString();
- bugreportFile = readFile(in);
+ bugreportLocationInfo = new BugreportLocationInfo(readFile(in));
int screenshotSize = in.readInt();
for (int i = 1; i <= screenshotSize; i++) {
- screenshotFiles.add(readFile(in));
+ screenshotLocationInfo = new ScreenshotLocationInfo(null);
+ screenshotLocationInfo.mScreenshotFiles.add(readFile(in));
}
finished.set(in.readInt() == 1);
@@ -2404,10 +2427,10 @@
dest.writeInt(lastProgress.intValue());
dest.writeLong(lastUpdate.longValue());
dest.writeString(getFormattedLastUpdate());
- writeFile(dest, bugreportFile);
+ writeFile(dest, bugreportLocationInfo.mBugreportFile);
- dest.writeInt(screenshotFiles.size());
- for (File screenshotFile : screenshotFiles) {
+ dest.writeInt(screenshotLocationInfo.mScreenshotFiles.size());
+ for (File screenshotFile : screenshotLocationInfo.mScreenshotFiles) {
writeFile(dest, screenshotFile);
}
@@ -2449,6 +2472,261 @@
};
}
+ /**
+ * Class for abstracting bugreport location. There are two possible cases:
+ * <li>If a bugreport request included a URI for bugreports of type {@link
+ * BugreportParams.BUGREPORT_MODE_WEAR}, then the URI file descriptor will be used. The
+ * requesting app manages the creation and lifecycle of the file.
+ * <li>If no URI is provided in the bugreport request, Shell will create a bugreport file and
+ * manage its lifecycle.
+ */
+ private static final class BugreportLocationInfo {
+ /** Path of the main bugreport file. */
+ @Nullable private File mBugreportFile;
+
+ /** Uri to bugreport location. */
+ @Nullable private Uri mBugreportUri;
+
+ BugreportLocationInfo(File bugreportFile) {
+ this.mBugreportFile = bugreportFile;
+ }
+
+ BugreportLocationInfo(Uri bugreportUri, File bugreportsDir, String baseName, String name) {
+ if (bugreportUri != null) {
+ this.mBugreportUri = bugreportUri;
+ } else {
+ this.mBugreportFile = new File(bugreportsDir, getFileName(".zip", baseName, name));
+ }
+ }
+
+ private boolean maybeCreateBugreportFile() {
+ if (mBugreportFile != null && mBugreportFile.exists()) {
+ Log.e(
+ TAG,
+ "Failed to start bugreport generation, the requested bugreport file "
+ + mBugreportFile
+ + " already exists");
+ return false;
+ }
+ createBugreportFile();
+ return true;
+ }
+
+ private void createBugreportFile() {
+ if (mBugreportUri == null) {
+ createReadWriteFile(mBugreportFile);
+ }
+ }
+
+ private ParcelFileDescriptor getBugreportFd(Context context) {
+ if (mBugreportUri != null) {
+ try {
+ return context.getContentResolver()
+ .openFileDescriptor(mBugreportUri, WRITE_AND_APPEND_MODE);
+ } catch (Exception e) {
+ Log.d(TAG, "Faced exception when getting BR file descriptor", e);
+ return null;
+ }
+ }
+ if (mBugreportFile == null) {
+ Log.e(TAG, "Could not get bugreport file descriptor; bugreport file was null");
+ return null;
+ }
+ return getFd(mBugreportFile);
+ }
+
+ private void maybeDeleteBugreportFile() {
+ if (mBugreportFile == null) {
+ // This means a URI is provided and shell is not responsible for the file's
+ // lifecycle.
+ return;
+ }
+ Log.i(TAG, "Deleting bugreport file " + mBugreportFile);
+ mBugreportFile.delete();
+ }
+
+ private boolean isValidBugreportResult() {
+ if (mBugreportFile != null) {
+ return mBugreportFile.exists() && mBugreportFile.canRead();
+ }
+ // If a bugreport uri was provided, we can't assert on whether the file exists and can
+ // be read. Assume the result is valid.
+ return true;
+ }
+
+ private void maybeDeleteEmptyBugreport() {
+ if (mBugreportFile == null) {
+ // This means a URI is provided and shell is not responsible for the file's
+ // lifecycle.
+ return;
+ }
+ if (mBugreportFile.length() == 0) {
+ Log.i(TAG, "Deleting empty bugreport file: " + mBugreportFile);
+ mBugreportFile.delete();
+ }
+ }
+
+ private void maybeRenameBugreportFile(BugreportInfo bugreportInfo) {
+ if (mBugreportFile == null) {
+ // This means a URI is provided and shell is not responsible for the file's naming.
+ return;
+ }
+ File newBugreportFile =
+ new File(mBugreportFile.getParentFile(), getFileName(bugreportInfo, ".zip"));
+ if (!newBugreportFile.getPath().equals(mBugreportFile.getPath())) {
+ if (mBugreportFile.renameTo(newBugreportFile)) {
+ mBugreportFile = newBugreportFile;
+ }
+ }
+ }
+
+ private boolean isPlainText() {
+ if (mBugreportFile != null) {
+ return mBugreportFile.getName().toLowerCase().endsWith(".txt");
+ }
+ return false;
+ }
+
+ private boolean isFileEmpty(Context context) {
+ if (mBugreportFile != null) {
+ return mBugreportFile.length() == 0;
+ }
+ return getBugreportFd(context).getStatSize() == 0;
+ }
+
+ @Override
+ public String toString() {
+ return "BugreportLocationInfo{"
+ + "bugreportFile="
+ + mBugreportFile
+ + ", bugreportUri="
+ + mBugreportUri
+ + '}';
+ }
+
+ private String getBugreportPath() {
+ if (mBugreportUri != null) {
+ return mBugreportUri.getLastPathSegment();
+ }
+ return mBugreportFile.getAbsolutePath();
+ }
+ }
+
+ /**
+ * Class for abstracting screenshot location. There are two possible cases:
+ * <li>If a bugreport request included a URI for bugreports of type {@link
+ * BugreportParams.BUGREPORT_MODE_WEAR}, then the URI file descriptor will be used. The
+ * requesting app manages the creation and lifecycle of the file.
+ * <li>If no URI is provided in the bugreport request, Shell will create the screenshot file and
+ * manage its lifecycle.
+ */
+ private static final class ScreenshotLocationInfo {
+
+ /** Uri to screenshot location. */
+ @Nullable private Uri mScreenshotUri;
+
+ /** Path to screenshot files. */
+ private List<File> mScreenshotFiles = new ArrayList<>(1);
+
+ ScreenshotLocationInfo(Uri screenshotUri) {
+ if (screenshotUri != null) {
+ this.mScreenshotUri = screenshotUri;
+ }
+ }
+
+ private ParcelFileDescriptor getScreenshotFd(Context context) {
+ if (mScreenshotUri != null) {
+ try {
+ return context.getContentResolver()
+ .openFileDescriptor(mScreenshotUri, WRITE_AND_APPEND_MODE);
+ } catch (Exception e) {
+ Log.d(TAG, "Faced exception when getting screenshot file", e);
+ return null;
+ }
+ }
+
+ if (mScreenshotFiles.isEmpty()) {
+ return null;
+ }
+ return getFd(mScreenshotFiles.getFirst());
+ }
+
+ @Override
+ public String toString() {
+ return "ScreenshotLocationInfo{"
+ + "screenshotUri="
+ + mScreenshotUri
+ + ", screenshotFiles="
+ + mScreenshotFiles
+ + '}';
+ }
+
+ private String getScreenshotPath() {
+ if (mScreenshotUri != null) {
+ return mScreenshotUri.getLastPathSegment();
+ }
+ return getScreenshotForIntent();
+ }
+
+ private void renameScreenshots(String initialName, String name) {
+ if (mScreenshotUri != null) {
+ // If a screenshot uri is provided, then shell is not responsible for the
+ // screenshot's naming.
+ return;
+ }
+ deleteEmptyScreenshots();
+ if (TextUtils.isEmpty(name) || mScreenshotFiles.isEmpty()) {
+ // If there is no user set name for screenshot file or there are no screenshot
+ // files, there's nothing to do.
+ return;
+ }
+ final List<File> renamedFiles = new ArrayList<>(mScreenshotFiles.size());
+ for (File oldFile : mScreenshotFiles) {
+ final String oldName = oldFile.getName();
+ final String newName = oldName.replaceFirst(initialName, name);
+ final File newFile;
+ if (!newName.equals(oldName)) {
+ final File renamedFile = new File(oldFile.getParentFile(), newName);
+ Log.d(TAG, "Renaming screenshot file " + oldFile + " to " + renamedFile);
+ newFile = oldFile.renameTo(renamedFile) ? renamedFile : oldFile;
+ } else {
+ Log.w(TAG, "Name didn't change: " + oldName);
+ newFile = oldFile;
+ }
+ if (newFile.length() > 0) {
+ renamedFiles.add(newFile);
+ } else if (newFile.delete()) {
+ Log.d(TAG, "screenshot file: " + newFile + " deleted successfully.");
+ }
+ }
+ mScreenshotFiles = renamedFiles;
+ }
+
+ private void deleteEmptyScreenshots() {
+ mScreenshotFiles.removeIf(
+ file -> {
+ final long length = file.length();
+ if (length == 0) {
+ Log.i(TAG, "Deleting empty screenshot file: " + file);
+ file.delete();
+ }
+ return length == 0;
+ });
+ }
+
+ /**
+ * Checks if screenshot array is non-empty and returns the first screenshot's path. The
+ * first screenshot is the default screenshot for the bugreport types that take it.
+ */
+ private String getScreenshotForIntent() {
+ if (!mScreenshotFiles.isEmpty()) {
+ final File screenshotFile = mScreenshotFiles.getFirst();
+ return screenshotFile.getAbsolutePath();
+ }
+ return null;
+ }
+ }
+
@GuardedBy("mLock")
private void checkProgressUpdatedLocked(BugreportInfo info, int progress) {
if (progress > CAPPED_PROGRESS) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
index 6d03118..0e35e1d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
@@ -45,6 +45,7 @@
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.Expandable
+import com.android.systemui.Flags
import com.android.systemui.animation.Expandable
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
@@ -56,7 +57,7 @@
/** [ComposeVolumePanelUiComponent] implementing a clickable button from a bottom row. */
class ButtonComponent(
private val viewModelFlow: StateFlow<ButtonViewModel?>,
- private val onClick: (expandable: Expandable, horizontalGravity: Int) -> Unit
+ private val onClick: (expandable: Expandable, horizontalGravity: Int) -> Unit,
) : ComposeVolumePanelUiComponent {
@Composable
@@ -84,14 +85,26 @@
},
color =
if (viewModel.isActive) {
- MaterialTheme.colorScheme.tertiaryContainer
+ if (Flags.volumeRedesign()) {
+ MaterialTheme.colorScheme.primary
+ } else {
+ MaterialTheme.colorScheme.tertiaryContainer
+ }
} else {
- MaterialTheme.colorScheme.surface
+ if (Flags.volumeRedesign()) {
+ MaterialTheme.colorScheme.surfaceContainerHigh
+ } else {
+ MaterialTheme.colorScheme.surface
+ }
},
shape = RoundedCornerShape(20.dp),
contentColor =
if (viewModel.isActive) {
- MaterialTheme.colorScheme.onTertiaryContainer
+ if (Flags.volumeRedesign()) {
+ MaterialTheme.colorScheme.onPrimary
+ } else {
+ MaterialTheme.colorScheme.onTertiaryContainer
+ }
} else {
MaterialTheme.colorScheme.onSurface
},
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index bb2daec..2cd7304 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -42,6 +42,7 @@
import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.Flags
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
@@ -51,7 +52,7 @@
/** [ComposeVolumePanelUiComponent] implementing a toggleable button from a bottom row. */
class ToggleButtonComponent(
private val viewModelFlow: StateFlow<ButtonViewModel?>,
- private val onCheckedChange: (isChecked: Boolean) -> Unit
+ private val onCheckedChange: (isChecked: Boolean) -> Unit,
) : ComposeVolumePanelUiComponent {
@Composable
@@ -68,15 +69,29 @@
BottomComponentButtonSurface {
val colors =
if (viewModel.isActive) {
- ButtonDefaults.buttonColors(
- containerColor = MaterialTheme.colorScheme.tertiaryContainer,
- contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
- )
+ if (Flags.volumeRedesign()) {
+ ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.primary,
+ contentColor = MaterialTheme.colorScheme.onPrimary,
+ )
+ } else {
+ ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+ contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
+ )
+ }
} else {
- ButtonDefaults.buttonColors(
- containerColor = Color.Transparent,
- contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
- )
+ if (Flags.volumeRedesign()) {
+ ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
+ contentColor = MaterialTheme.colorScheme.onSurface,
+ )
+ } else {
+ ButtonDefaults.buttonColors(
+ containerColor = Color.Transparent,
+ contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
+ )
+ }
}
Button(
modifier =
@@ -93,7 +108,7 @@
onClick = { onCheckedChange(!viewModel.isActive) },
shape = RoundedCornerShape(20.dp),
colors = colors,
- contentPadding = PaddingValues(0.dp)
+ contentPadding = PaddingValues(0.dp),
) {
Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index 581fb9d..25892c5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -37,11 +37,13 @@
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
@@ -51,8 +53,11 @@
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.PlatformIconButton
import com.android.compose.PlatformSliderColors
import com.android.compose.modifiers.padding
+import com.android.compose.modifiers.thenIf
+import com.android.systemui.Flags
import com.android.systemui.res.R
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel
@@ -84,7 +89,11 @@
val sliderPadding by topSliderPadding(isExpandable)
VolumeSlider(
- modifier = Modifier.padding(end = { sliderPadding.roundToPx() }).fillMaxWidth(),
+ modifier =
+ Modifier.thenIf(!Flags.volumeRedesign()) {
+ Modifier.padding(end = { sliderPadding.roundToPx() })
+ }
+ .fillMaxWidth(),
state = sliderState,
onValueChange = { newValue: Float ->
sliderViewModel.onValueChanged(sliderState, newValue)
@@ -93,15 +102,29 @@
onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
sliderColors = sliderColors,
hapticsViewModelFactory = sliderViewModel.getSliderHapticsViewModelFactory(),
+ button =
+ if (Flags.volumeRedesign()) {
+ {
+ ExpandButton(
+ isExpanded = isExpanded,
+ isExpandable = isExpandable,
+ onExpandedChanged = onExpandedChanged,
+ )
+ }
+ } else {
+ null
+ },
)
- ExpandButton(
- modifier = Modifier.align(Alignment.CenterEnd),
- isExpanded = isExpanded,
- isExpandable = isExpandable,
- onExpandedChanged = onExpandedChanged,
- sliderColors = sliderColors,
- )
+ if (!Flags.volumeRedesign()) {
+ ExpandButtonLegacy(
+ modifier = Modifier.align(Alignment.CenterEnd),
+ isExpanded = isExpanded,
+ isExpandable = isExpandable,
+ onExpandedChanged = onExpandedChanged,
+ sliderColors = sliderColors,
+ )
+ }
}
AnimatedVisibility(
visible = isExpanded || !isExpandable,
@@ -153,7 +176,7 @@
}
@Composable
-private fun ExpandButton(
+private fun ExpandButtonLegacy(
isExpanded: Boolean,
isExpandable: Boolean,
onExpandedChanged: (Boolean) -> Unit,
@@ -200,6 +223,48 @@
}
}
+@Composable
+private fun ExpandButton(
+ isExpanded: Boolean,
+ isExpandable: Boolean,
+ onExpandedChanged: (Boolean) -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ val expandButtonStateDescription =
+ if (isExpanded) {
+ stringResource(R.string.volume_panel_expanded_sliders)
+ } else {
+ stringResource(R.string.volume_panel_collapsed_sliders)
+ }
+ AnimatedVisibility(
+ modifier = modifier,
+ visible = isExpandable,
+ enter = expandButtonEnterTransition(),
+ exit = expandButtonExitTransition(),
+ ) {
+ PlatformIconButton(
+ modifier =
+ Modifier.size(width = 48.dp, height = 40.dp).semantics {
+ role = Role.Switch
+ stateDescription = expandButtonStateDescription
+ },
+ onClick = { onExpandedChanged(!isExpanded) },
+ colors =
+ IconButtonDefaults.iconButtonColors(
+ containerColor = Color.Transparent,
+ contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
+ ),
+ iconResource =
+ if (isExpanded) {
+ R.drawable.ic_arrow_down_24dp
+ } else {
+ R.drawable.ic_arrow_up_24dp
+ },
+ contentDescription = null,
+ )
+ }
+}
+
private fun enterTransition(index: Int, totalCount: Int): EnterTransition {
val enterDelay = ((totalCount - index + 1) * 10).coerceAtLeast(0)
val enterDuration = (EXPAND_DURATION_MILLIS - enterDelay).coerceAtLeast(100)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index 97ce429..fa5f72b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -24,9 +24,18 @@
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Slider
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
@@ -48,6 +57,7 @@
import androidx.compose.ui.unit.dp
import com.android.compose.PlatformSlider
import com.android.compose.PlatformSliderColors
+import com.android.systemui.Flags
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.compose.modifiers.sysuiResTag
@@ -61,11 +71,104 @@
fun VolumeSlider(
state: SliderState,
onValueChange: (newValue: Float) -> Unit,
- onValueChangeFinished: (() -> Unit)? = null,
onIconTapped: () -> Unit,
+ sliderColors: PlatformSliderColors,
modifier: Modifier = Modifier,
+ hapticsViewModelFactory: SliderHapticsViewModel.Factory?,
+ onValueChangeFinished: (() -> Unit)? = null,
+ button: (@Composable () -> Unit)? = null,
+) {
+ if (!Flags.volumeRedesign()) {
+ LegacyVolumeSlider(
+ state = state,
+ onValueChange = onValueChange,
+ onIconTapped = onIconTapped,
+ sliderColors = sliderColors,
+ onValueChangeFinished = onValueChangeFinished,
+ modifier = modifier,
+ hapticsViewModelFactory = hapticsViewModelFactory,
+ )
+ return
+ }
+
+ val value by valueState(state)
+ Column(modifier) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(12.dp),
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ state.icon?.let {
+ Icon(
+ icon = it,
+ tint = MaterialTheme.colorScheme.onSurface,
+ modifier = Modifier.size(40.dp).padding(8.dp),
+ )
+ }
+ Text(
+ text = state.label,
+ style = MaterialTheme.typography.titleMedium,
+ color = MaterialTheme.colorScheme.onSurface,
+ modifier = Modifier.weight(1f).align(Alignment.CenterVertically),
+ )
+ button?.invoke()
+ }
+ Slider(
+ value = value,
+ valueRange = state.valueRange,
+ onValueChange = onValueChange,
+ onValueChangeFinished = onValueChangeFinished,
+ enabled = state.isEnabled,
+ modifier =
+ Modifier.height(40.dp).sysuiResTag(state.label).clearAndSetSemantics {
+ if (state.isEnabled) {
+ contentDescription = state.label
+ state.a11yClickDescription?.let {
+ customActions =
+ listOf(
+ CustomAccessibilityAction(it) {
+ onIconTapped()
+ true
+ }
+ )
+ }
+
+ state.a11yStateDescription?.let { stateDescription = it }
+ progressBarRangeInfo = ProgressBarRangeInfo(state.value, state.valueRange)
+ } else {
+ disabled()
+ contentDescription =
+ state.disabledMessage?.let { "${state.label}, $it" } ?: state.label
+ }
+ setProgress { targetValue ->
+ val targetDirection =
+ when {
+ targetValue > value -> 1
+ targetValue < value -> -1
+ else -> 0
+ }
+
+ val newValue =
+ (value + targetDirection * state.a11yStep).coerceIn(
+ state.valueRange.start,
+ state.valueRange.endInclusive,
+ )
+ onValueChange(newValue)
+ true
+ }
+ },
+ )
+ }
+}
+
+@Composable
+private fun LegacyVolumeSlider(
+ state: SliderState,
+ onValueChange: (newValue: Float) -> Unit,
+ onIconTapped: () -> Unit,
sliderColors: PlatformSliderColors,
hapticsViewModelFactory: SliderHapticsViewModel.Factory?,
+ modifier: Modifier = Modifier,
+ onValueChangeFinished: (() -> Unit)? = null,
) {
val value by valueState(state)
val interactionSource = remember { MutableInteractionSource() }
@@ -178,7 +281,7 @@
val shouldSkipAnimation =
prevState is SliderState.Empty || prevState.isEnabled != state.isEnabled
val value =
- if (shouldSkipAnimation) mutableFloatStateOf(state.value)
+ if (shouldSkipAnimation) remember { mutableFloatStateOf(state.value) }
else animateFloatAsState(targetValue = state.value, label = "VolumeSliderValueAnimation")
prevState = state
return value
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSliderContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSliderContent.kt
index 4ae4eb8..28226ff 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSliderContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSliderContent.kt
@@ -53,7 +53,7 @@
DisabledMessage,
}
-/** Shows label of the [VolumeSlider]. Also shows [disabledMessage] when not [isEnabled]. */
+/** Shows label of the [LegacyVolumeSlider]. Also shows [disabledMessage] when not [isEnabled]. */
@Composable
fun VolumeSliderContent(
label: String,
@@ -89,7 +89,7 @@
}
}
},
- measurePolicy = VolumeSliderContentMeasurePolicy(isEnabled)
+ measurePolicy = VolumeSliderContentMeasurePolicy(isEnabled),
)
}
@@ -102,7 +102,7 @@
override fun MeasureScope.measure(
measurables: List<Measurable>,
- constraints: Constraints
+ constraints: Constraints,
): MeasureResult {
val labelPlaceable =
measurables
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index 0fc88b2..a4237f3 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -225,7 +225,7 @@
return animateElementValueAsState(value, key, SharedColorType, canOverflow = false)
}
-private object SharedColorType : SharedValueType<Color, ColorDelta> {
+internal object SharedColorType : SharedValueType<Color, ColorDelta> {
override val unspecifiedValue: Color = Color.Unspecified
override val zeroDeltaValue: ColorDelta = ColorDelta(0f, 0f, 0f, 0f)
@@ -255,17 +255,17 @@
alpha = aOklab.alpha + b.alpha * bWeight,
colorSpace = ColorSpaces.Oklab,
)
- .convert(aOklab.colorSpace)
+ .convert(a.colorSpace)
}
}
/**
- * Represents the diff between two colors in the same color space.
+ * Represents the diff between two colors in the Oklab color space.
*
* Note: This class is necessary because Color() checks the bounds of its values and UncheckedColor
* is internal.
*/
-private class ColorDelta(val red: Float, val green: Float, val blue: Float, val alpha: Float)
+internal class ColorDelta(val red: Float, val green: Float, val blue: Float, val alpha: Float)
@Composable
internal fun <T> animateSharedValueAsState(
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
index 3644b30..2fd1d8d8 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -18,7 +18,10 @@
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
@@ -495,4 +498,13 @@
assertThat(lastValues[SceneA]).isWithin(0.001f).of(100f)
assertThat(lastValues[SceneB]).isWithin(0.001f).of(100f)
}
+
+ @Test
+ fun interpolatedColor() {
+ val a = Color.Red
+ val b = Color.Green
+ val delta = SharedColorType.diff(b, a) // b - a
+ val interpolated = SharedColorType.addWeighted(a, delta, 0.5f) // a + (b - a) * 0.5f
+ rule.setContent { Box(Modifier.fillMaxSize().background(interpolated)) }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index 9d471f4..ad12c61 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -139,13 +139,11 @@
private ActivityInfo mActivityInfo;
@Mock
private Drawable mDrawable;
- @Mock
- private HearingDevicesPresetsController mPresetsController;
+
private SystemUIDialog mDialog;
private SystemUIDialog.Factory mDialogFactory;
private HearingDevicesDialogDelegate mDialogDelegate;
private TestableLooper mTestableLooper;
- private final List<CachedBluetoothDevice> mDevices = new ArrayList<>();
@Before
public void setUp() {
@@ -155,7 +153,7 @@
when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
when(mLocalBluetoothAdapter.isEnabled()).thenReturn(true);
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
- when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices);
+ when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(List.of(mCachedDevice));
when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState);
when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
@@ -163,6 +161,7 @@
when(mCachedDevice.getDevice()).thenReturn(mDevice);
when(mCachedDevice.getAddress()).thenReturn(DEVICE_ADDRESS);
when(mCachedDevice.getName()).thenReturn(DEVICE_NAME);
+ when(mCachedDevice.getProfiles()).thenReturn(List.of(mHapClientProfile));
when(mCachedDevice.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true);
when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true);
when(mCachedDevice.isConnectedHapClientDevice()).thenReturn(true);
@@ -170,12 +169,11 @@
when(mHearingDeviceItem.getCachedBluetoothDevice()).thenReturn(mCachedDevice);
mContext.setMockPackageManager(mPackageManager);
- mDevices.add(mCachedDevice);
}
@Test
public void clickPairNewDeviceButton_intentActionMatch() {
- setUpPairNewDeviceDialog();
+ setUpDeviceDialogWithPairNewDeviceButton();
mDialog.show();
getPairNewDeviceButton(mDialog).performClick();
@@ -191,7 +189,7 @@
@Test
public void onDeviceItemGearClicked_intentActionMatch() {
- setUpDeviceListDialog();
+ setUpDeviceDialogWithoutPairNewDeviceButton();
mDialogDelegate.onDeviceItemGearClicked(mHearingDeviceItem, new View(mContext));
@@ -206,7 +204,7 @@
@Test
public void onDeviceItemOnClicked_connectedDevice_disconnect() {
- setUpDeviceListDialog();
+ setUpDeviceDialogWithoutPairNewDeviceButton();
when(mHearingDeviceItem.getType()).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE);
mDialogDelegate.onDeviceItemClicked(mHearingDeviceItem, new View(mContext));
@@ -222,7 +220,7 @@
mContext.getOrCreateTestableResources().addOverride(
R.array.config_quickSettingsHearingDevicesRelatedToolName, new String[]{});
- setUpPairNewDeviceDialog();
+ setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
assertToolsUi(0);
@@ -237,7 +235,7 @@
mContext.getOrCreateTestableResources().addOverride(
R.array.config_quickSettingsHearingDevicesRelatedToolName, new String[]{});
- setUpPairNewDeviceDialog();
+ setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
assertToolsUi(1);
@@ -247,9 +245,8 @@
@EnableFlags(Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS)
public void showDialog_hasLiveCaption_oneRelatedToolInConfig_showTwoRelatedTools()
throws PackageManager.NameNotFoundException {
- when(mPackageManager.queryIntentActivities(
- eq(LIVE_CAPTION_INTENT), anyInt())).thenReturn(
- List.of(new ResolveInfo()));
+ when(mPackageManager.queryIntentActivities(eq(LIVE_CAPTION_INTENT), anyInt()))
+ .thenReturn(List.of(new ResolveInfo()));
mContext.getOrCreateTestableResources().addOverride(
R.array.config_quickSettingsHearingDevicesRelatedToolName,
new String[]{TEST_PKG + "/" + TEST_CLS});
@@ -260,18 +257,18 @@
when(mActivityInfo.getComponentName()).thenReturn(TEST_COMPONENT);
when(mDrawable.mutate()).thenReturn(mDrawable);
- setUpPairNewDeviceDialog();
+ setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
assertToolsUi(2);
}
@Test
- public void showDialog_noPreset_presetGone() {
- when(mPresetsController.getAllPresetInfo()).thenReturn(new ArrayList<>());
- when(mPresetsController.getActivePresetIndex()).thenReturn(PRESET_INDEX_UNAVAILABLE);
+ public void showDialog_noPreset_presetLayoutGone() {
+ when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(new ArrayList<>());
+ when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(PRESET_INDEX_UNAVAILABLE);
- setUpDeviceListDialog();
+ setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
ViewGroup presetLayout = getPresetLayout(mDialog);
@@ -281,11 +278,12 @@
@Test
public void showDialog_presetExist_presetSelected() {
BluetoothHapPresetInfo info = getTestPresetInfo();
- when(mPresetsController.getAllPresetInfo()).thenReturn(List.of(info));
- when(mPresetsController.getActivePresetIndex()).thenReturn(TEST_PRESET_INDEX);
+ when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
+ when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(TEST_PRESET_INDEX);
- setUpDeviceListDialog();
+ setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
+ mTestableLooper.processAllMessages();
ViewGroup presetLayout = getPresetLayout(mDialog);
assertThat(presetLayout.getVisibility()).isEqualTo(View.VISIBLE);
@@ -295,48 +293,32 @@
@Test
public void onActiveDeviceChanged_presetExist_presetSelected() {
- setUpDeviceListDialog();
+ setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
BluetoothHapPresetInfo info = getTestPresetInfo();
- when(mPresetsController.getAllPresetInfo()).thenReturn(List.of(info));
- when(mPresetsController.getActivePresetIndex()).thenReturn(TEST_PRESET_INDEX);
+ when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
+ when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(TEST_PRESET_INDEX);
+
+ Spinner spinner = getPresetSpinner(mDialog);
+ assertThat(spinner.getSelectedItemPosition()).isEqualTo(-1);
mDialogDelegate.onActiveDeviceChanged(mCachedDevice, BluetoothProfile.LE_AUDIO);
mTestableLooper.processAllMessages();
ViewGroup presetLayout = getPresetLayout(mDialog);
assertThat(presetLayout.getVisibility()).isEqualTo(View.VISIBLE);
- Spinner spinner = getPresetSpinner(mDialog);
assertThat(spinner.getSelectedItemPosition()).isEqualTo(0);
}
-
-
- private void setUpPairNewDeviceDialog() {
- mDialogFactory = new SystemUIDialog.Factory(
- mContext,
- mSystemUIDialogManager,
- mSysUiState,
- getFakeBroadcastDispatcher(),
- mDialogTransitionAnimator
- );
- mDialogDelegate = new HearingDevicesDialogDelegate(
- mContext,
- true,
- TEST_LAUNCH_SOURCE_ID,
- mDialogFactory,
- mActivityStarter,
- mDialogTransitionAnimator,
- mLocalBluetoothManager,
- new Handler(mTestableLooper.getLooper()),
- mAudioManager,
- mUiEventLogger
- );
-
- mDialog = mDialogDelegate.createDialog();
+ private void setUpDeviceDialogWithPairNewDeviceButton() {
+ setUpDeviceDialog(/* showPairNewDevice= */ true);
}
- private void setUpDeviceListDialog() {
+ private void setUpDeviceDialogWithoutPairNewDeviceButton() {
+ setUpDeviceDialog(/* showPairNewDevice= */ false);
+ }
+
+ private void setUpDeviceDialog(boolean showPairNewDevice) {
mDialogFactory = new SystemUIDialog.Factory(
mContext,
mSystemUIDialogManager,
@@ -345,8 +327,7 @@
mDialogTransitionAnimator
);
mDialogDelegate = new HearingDevicesDialogDelegate(
- mContext,
- false,
+ showPairNewDevice,
TEST_LAUNCH_SOURCE_ID,
mDialogFactory,
mActivityStarter,
@@ -356,15 +337,14 @@
mAudioManager,
mUiEventLogger
);
-
mDialog = mDialogDelegate.createDialog();
- mDialogDelegate.setHearingDevicesPresetsController(mPresetsController);
}
private BluetoothHapPresetInfo getTestPresetInfo() {
BluetoothHapPresetInfo info = mock(BluetoothHapPresetInfo.class);
when(info.getName()).thenReturn(TEST_PRESET_NAME);
when(info.getIndex()).thenReturn(TEST_PRESET_INDEX);
+ when(info.isAvailable()).thenReturn(true);
return info;
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java
index 2ac5d10..c9779c9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java
@@ -21,10 +21,10 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyList;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.mockito.kotlin.VerificationKt.never;
import static java.util.Collections.emptyList;
@@ -39,7 +39,6 @@
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HapClientProfile;
-import com.android.settingslib.bluetooth.LocalBluetoothProfile;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.systemui.SysuiTestCase;
@@ -53,6 +52,7 @@
import org.mockito.junit.MockitoRule;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.Executor;
/** Tests for {@link HearingDevicesPresetsController}. */
@@ -62,6 +62,7 @@
public class HearingDevicesPresetsControllerTest extends SysuiTestCase {
private static final int TEST_PRESET_INDEX = 1;
+ private static final int TEST_UPDATED_PRESET_INDEX = 2;
private static final String TEST_PRESET_NAME = "test_preset";
private static final int TEST_HAP_GROUP_ID = 1;
private static final int TEST_REASON = 1024;
@@ -74,14 +75,13 @@
@Mock
private HapClientProfile mHapClientProfile;
@Mock
- private CachedBluetoothDevice mCachedBluetoothDevice;
+ private CachedBluetoothDevice mCachedDevice;
@Mock
- private CachedBluetoothDevice mSubCachedBluetoothDevice;
+ private CachedBluetoothDevice mCachedMemberDevice;
@Mock
- private BluetoothDevice mBluetoothDevice;
+ private BluetoothDevice mDevice;
@Mock
- private BluetoothDevice mSubBluetoothDevice;
-
+ private BluetoothDevice mMemberDevice;
@Mock
private HearingDevicesPresetsController.PresetCallback mCallback;
@@ -91,15 +91,19 @@
public void setUp() {
when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
when(mHapClientProfile.isProfileReady()).thenReturn(true);
- when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
- when(mCachedBluetoothDevice.getSubDevice()).thenReturn(mSubCachedBluetoothDevice);
- when(mSubCachedBluetoothDevice.getDevice()).thenReturn(mSubBluetoothDevice);
+ when(mCachedDevice.getDevice()).thenReturn(mDevice);
+ when(mCachedDevice.getProfiles()).thenReturn(List.of(mHapClientProfile));
+ when(mCachedDevice.getMemberDevice()).thenReturn(Set.of(mCachedMemberDevice));
+ when(mCachedMemberDevice.getDevice()).thenReturn(mMemberDevice);
mController = new HearingDevicesPresetsController(mProfileManager, mCallback);
+ mController.setDevice(mCachedDevice);
}
@Test
public void onServiceConnected_callExpectedCallback() {
+ preparePresetInfo(/* isValid= */ true);
+
mController.onServiceConnected();
verify(mHapClientProfile).registerCallback(any(Executor.class),
@@ -108,115 +112,129 @@
}
@Test
- public void getAllPresetInfo_setInvalidHearingDevice_getEmpty() {
- when(mCachedBluetoothDevice.getProfiles()).thenReturn(emptyList());
- mController.setHearingDeviceIfSupportHap(mCachedBluetoothDevice);
- BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true);
- when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn(
- List.of(hapPresetInfo));
+ public void setDevice_nonHapDevice_getEmptyListAndInvalidActiveIndex() {
+ when(mCachedDevice.getProfiles()).thenReturn(emptyList());
+ preparePresetInfo(/* isValid= */ true);
+
+ mController.setDevice(mCachedDevice);
+
+ assertThat(mController.getAllPresetInfo()).isEmpty();
+ assertThat(mController.getActivePresetIndex()).isEqualTo(
+ BluetoothHapClient.PRESET_INDEX_UNAVAILABLE);
+ }
+
+ @Test
+ public void refreshPresetInfo_containsOnlyNotAvailablePresetInfo_getEmptyList() {
+ preparePresetInfo(/* isValid= */ false);
+
+ mController.refreshPresetInfo();
assertThat(mController.getAllPresetInfo()).isEmpty();
}
@Test
- public void getAllPresetInfo_containsNotAvailablePresetInfo_getEmpty() {
- setValidHearingDeviceSupportHap();
- BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(false);
- when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn(
- List.of(hapPresetInfo));
+ public void refreshPresetInfo_containsOnePresetInfo_getOnePresetInfo() {
+ List<BluetoothHapPresetInfo> infos = preparePresetInfo(/* isValid= */ true);
- assertThat(mController.getAllPresetInfo()).isEmpty();
+ mController.refreshPresetInfo();
+
+ List<BluetoothHapPresetInfo> presetInfos = mController.getAllPresetInfo();
+ assertThat(presetInfos.size()).isEqualTo(1);
+ assertThat(presetInfos).contains(infos.getFirst());
}
@Test
- public void getAllPresetInfo_containsOnePresetInfo_getOnePresetInfo() {
- setValidHearingDeviceSupportHap();
- BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true);
- when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn(
- List.of(hapPresetInfo));
+ public void refreshPresetInfo_getExpectedIndex() {
+ preparePresetInfo(/* isValid= */ true);
- assertThat(mController.getAllPresetInfo()).contains(hapPresetInfo);
- }
-
- @Test
- public void getActivePresetIndex_getExpectedIndex() {
- setValidHearingDeviceSupportHap();
- when(mHapClientProfile.getActivePresetIndex(mBluetoothDevice)).thenReturn(
- TEST_PRESET_INDEX);
+ mController.refreshPresetInfo();
assertThat(mController.getActivePresetIndex()).isEqualTo(TEST_PRESET_INDEX);
}
@Test
- public void onPresetSelected_presetIndex_callOnPresetInfoUpdatedWithExpectedPresetIndex() {
- setValidHearingDeviceSupportHap();
- BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true);
- when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn(
- List.of(hapPresetInfo));
- when(mHapClientProfile.getActivePresetIndex(mBluetoothDevice)).thenReturn(
- TEST_PRESET_INDEX);
+ public void refreshPresetInfo_callbackIsCalledWhenNeeded() {
+ List<BluetoothHapPresetInfo> infos = preparePresetInfo(/* isValid= */ true);
- mController.onPresetSelected(mBluetoothDevice, TEST_PRESET_INDEX, TEST_REASON);
+ mController.refreshPresetInfo();
- verify(mCallback).onPresetInfoUpdated(eq(List.of(hapPresetInfo)), eq(TEST_PRESET_INDEX));
+ verify(mCallback).onPresetInfoUpdated(infos, TEST_PRESET_INDEX);
+
+ Mockito.reset(mCallback);
+ mController.refreshPresetInfo();
+
+ verify(mCallback, never()).onPresetInfoUpdated(anyList(), anyInt());
+
+ Mockito.reset(mCallback);
+ when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(TEST_UPDATED_PRESET_INDEX);
+ mController.refreshPresetInfo();
+
+ verify(mCallback).onPresetInfoUpdated(infos, TEST_UPDATED_PRESET_INDEX);
}
@Test
- public void onPresetInfoChanged_presetIndex_callOnPresetInfoUpdatedWithExpectedPresetIndex() {
- setValidHearingDeviceSupportHap();
- BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true);
- when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn(
- List.of(hapPresetInfo));
- when(mHapClientProfile.getActivePresetIndex(mBluetoothDevice)).thenReturn(
- TEST_PRESET_INDEX);
+ public void onPresetSelected_callOnPresetInfoUpdatedWithExpectedPresetIndex() {
+ List<BluetoothHapPresetInfo> infos = preparePresetInfo(/* isValid= */ true);
- mController.onPresetInfoChanged(mBluetoothDevice, List.of(hapPresetInfo), TEST_REASON);
+ mController.onPresetSelected(mDevice, TEST_PRESET_INDEX, TEST_REASON);
- verify(mCallback).onPresetInfoUpdated(List.of(hapPresetInfo), TEST_PRESET_INDEX);
+ verify(mCallback).onPresetInfoUpdated(infos, TEST_PRESET_INDEX);
+ }
+
+ @Test
+ public void onPresetInfoChanged_callOnPresetInfoUpdatedWithExpectedPresetIndex() {
+ List<BluetoothHapPresetInfo> infos = preparePresetInfo(/* isValid= */ true);
+
+ mController.onPresetInfoChanged(mDevice, infos, TEST_REASON);
+
+ verify(mCallback).onPresetInfoUpdated(infos, TEST_PRESET_INDEX);
}
@Test
public void onPresetSelectionFailed_callOnPresetCommandFailed() {
- setValidHearingDeviceSupportHap();
-
- mController.onPresetSelectionFailed(mBluetoothDevice, TEST_REASON);
+ mController.onPresetSelectionFailed(mDevice, TEST_REASON);
verify(mCallback).onPresetCommandFailed(TEST_REASON);
}
@Test
public void onSetPresetNameFailed_callOnPresetCommandFailed() {
- setValidHearingDeviceSupportHap();
-
- mController.onSetPresetNameFailed(mBluetoothDevice, TEST_REASON);
+ mController.onSetPresetNameFailed(mDevice, TEST_REASON);
verify(mCallback).onPresetCommandFailed(TEST_REASON);
}
@Test
- public void onPresetSelectionForGroupFailed_callSelectPresetIndividual() {
- setValidHearingDeviceSupportHap();
+ public void onPresetSelectionForGroupFailed_callSelectPresetIndependently() {
mController.selectPreset(TEST_PRESET_INDEX);
Mockito.reset(mHapClientProfile);
- when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn(TEST_HAP_GROUP_ID);
+ when(mHapClientProfile.getHapGroup(mDevice)).thenReturn(TEST_HAP_GROUP_ID);
mController.onPresetSelectionForGroupFailed(TEST_HAP_GROUP_ID, TEST_REASON);
-
- verify(mHapClientProfile).selectPreset(mBluetoothDevice, TEST_PRESET_INDEX);
- verify(mHapClientProfile).selectPreset(mSubBluetoothDevice, TEST_PRESET_INDEX);
+ verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX);
+ verify(mHapClientProfile).selectPreset(mMemberDevice, TEST_PRESET_INDEX);
}
@Test
public void onSetPresetNameForGroupFailed_callOnPresetCommandFailed() {
- setValidHearingDeviceSupportHap();
-
mController.onSetPresetNameForGroupFailed(TEST_HAP_GROUP_ID, TEST_REASON);
verify(mCallback).onPresetCommandFailed(TEST_REASON);
}
@Test
+ public void registerHapCallback_profileNotReady_addServiceListener() {
+ when(mHapClientProfile.isProfileReady()).thenReturn(false);
+
+ mController.registerHapCallback();
+
+ verify(mProfileManager).addServiceListener(mController);
+ verify(mHapClientProfile, never()).registerCallback(any(Executor.class),
+ any(BluetoothHapClient.Callback.class));
+ }
+
+ @Test
public void registerHapCallback_callHapRegisterCallback() {
mController.registerHapCallback();
@@ -233,9 +251,8 @@
@Test
public void selectPreset_supportSynchronized_validGroupId_callSelectPresetForGroup() {
- setValidHearingDeviceSupportHap();
- when(mHapClientProfile.supportsSynchronizedPresets(mBluetoothDevice)).thenReturn(true);
- when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn(TEST_HAP_GROUP_ID);
+ when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(true);
+ when(mHapClientProfile.getHapGroup(mDevice)).thenReturn(TEST_HAP_GROUP_ID);
mController.selectPreset(TEST_PRESET_INDEX);
@@ -243,28 +260,34 @@
}
@Test
- public void selectPreset_supportSynchronized_invalidGroupId_callSelectPresetIndividual() {
- setValidHearingDeviceSupportHap();
- when(mHapClientProfile.supportsSynchronizedPresets(mBluetoothDevice)).thenReturn(true);
- when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn(
+ public void selectPreset_supportSynchronized_invalidGroupId_callSelectPresetIndependently() {
+ when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(true);
+ when(mHapClientProfile.getHapGroup(mDevice)).thenReturn(
BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
mController.selectPreset(TEST_PRESET_INDEX);
- verify(mHapClientProfile).selectPreset(mBluetoothDevice, TEST_PRESET_INDEX);
- verify(mHapClientProfile).selectPreset(mSubBluetoothDevice, TEST_PRESET_INDEX);
+ verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX);
+ verify(mHapClientProfile).selectPreset(mMemberDevice, TEST_PRESET_INDEX);
}
@Test
- public void selectPreset_notSupportSynchronized_validGroupId_callSelectPresetIndividual() {
- setValidHearingDeviceSupportHap();
- when(mHapClientProfile.supportsSynchronizedPresets(mBluetoothDevice)).thenReturn(false);
- when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn(TEST_HAP_GROUP_ID);
+ public void selectPreset_notSupportSynchronized_validGroupId_callSelectPresetIndependently() {
+ when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(false);
+ when(mHapClientProfile.getHapGroup(mDevice)).thenReturn(TEST_HAP_GROUP_ID);
mController.selectPreset(TEST_PRESET_INDEX);
- verify(mHapClientProfile).selectPreset(mBluetoothDevice, TEST_PRESET_INDEX);
- verify(mHapClientProfile).selectPreset(mSubBluetoothDevice, TEST_PRESET_INDEX);
+ verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX);
+ verify(mHapClientProfile).selectPreset(mMemberDevice, TEST_PRESET_INDEX);
+ }
+
+ private List<BluetoothHapPresetInfo> preparePresetInfo(boolean isValid) {
+ BluetoothHapPresetInfo info = getHapPresetInfo(isValid);
+ List<BluetoothHapPresetInfo> infos = List.of(info);
+ when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(infos);
+ when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(TEST_PRESET_INDEX);
+ return infos;
}
private BluetoothHapPresetInfo getHapPresetInfo(boolean available) {
@@ -274,12 +297,4 @@
when(info.isAvailable()).thenReturn(available);
return info;
}
-
- private void setValidHearingDeviceSupportHap() {
- LocalBluetoothProfile hapClientProfile = mock(HapClientProfile.class);
- List<LocalBluetoothProfile> profiles = List.of(hapClientProfile);
- when(mCachedBluetoothDevice.getProfiles()).thenReturn(profiles);
-
- mController.setHearingDeviceIfSupportHap(mCachedBluetoothDevice);
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 2817f55..acc97a9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -43,6 +43,7 @@
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
+import android.app.KeyguardManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -182,6 +183,8 @@
@Captor
private ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> mFaceAuthenticatorsRegisteredCaptor;
@Captor
+ private ArgumentCaptor<KeyguardManager.KeyguardLockedStateListener> mKeyguardLockedStateCaptor;
+ @Captor
private ArgumentCaptor<BiometricStateListener> mBiometricStateCaptor;
@Captor
private ArgumentCaptor<Integer> mModalityCaptor;
@@ -192,6 +195,8 @@
@Mock
private VibratorHelper mVibratorHelper;
@Mock
+ private KeyguardManager mKeyguardManager;
+ @Mock
private MSDLPlayer mMSDLPlayer;
private TestableContext mContextSpy;
@@ -272,6 +277,9 @@
mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(fpProps);
mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(faceProps);
+ verify(mKeyguardManager).addKeyguardLockedStateListener(any(),
+ mKeyguardLockedStateCaptor.capture());
+
// Ensures that the operations posted on the handler get executed.
waitForIdleSync();
}
@@ -977,6 +985,18 @@
}
@Test
+ public void testCloseDialog_whenDeviceLocks() throws Exception {
+ showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
+
+ mKeyguardLockedStateCaptor.getValue().onKeyguardLockedStateChanged(
+ true /* isKeyguardLocked */);
+
+ verify(mReceiver).onDialogDismissed(
+ eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL),
+ eq(null) /* credentialAttestation */);
+ }
+
+ @Test
public void testShowDialog_whenOwnerNotInForeground() {
PromptInfo promptInfo = createTestPromptInfo();
promptInfo.setAllowBackgroundAuthentication(false);
@@ -1193,7 +1213,7 @@
mWakefulnessLifecycle, mUserManager, mLockPatternUtils, () -> mUdfpsLogger,
() -> mLogContextInteractor, () -> mPromptSelectionInteractor,
() -> mCredentialViewModel, () -> mPromptViewModel, mInteractionJankMonitor,
- mHandler, mBackgroundExecutor, mUdfpsUtils, mVibratorHelper,
+ mHandler, mBackgroundExecutor, mUdfpsUtils, mVibratorHelper, mKeyguardManager,
mLazyViewCapture, mMSDLPlayer);
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
index 4e64c50..297aee5c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
@@ -17,6 +17,8 @@
package com.android.systemui.biometrics.domain.interactor
import android.graphics.Rect
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.view.MotionEvent
import android.view.Surface
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -24,6 +26,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.authController
+import com.android.systemui.biometrics.fingerprintManager
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
@@ -39,6 +42,8 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.verify
@@ -57,6 +62,8 @@
private val testScope: TestScope = kosmos.testScope
private val authController: AuthController = kosmos.authController
+ private val fingerprintManager: FingerprintManager = kosmos.fingerprintManager
+ @Mock private lateinit var fingerprintSensorProperties: FingerprintSensorPropertiesInternal
@Captor private lateinit var authControllerCallback: ArgumentCaptor<AuthController.Callback>
@Mock private lateinit var udfpsOverlayParams: UdfpsOverlayParams
@@ -122,6 +129,20 @@
context.orCreateTestableResources.removeOverride(R.dimen.pixel_pitch)
}
+ @Test
+ fun testSetIgnoreDisplayTouches() =
+ testScope.runTest {
+ createUdfpsOverlayInteractor()
+ whenever(authController.isUdfpsSupported).thenReturn(true)
+ whenever(authController.udfpsProps).thenReturn(listOf(fingerprintSensorProperties))
+
+ underTest.setHandleTouches(false)
+ verify(fingerprintManager).setIgnoreDisplayTouches(anyLong(), anyInt(), eq(true))
+
+ underTest.setHandleTouches(true)
+ verify(fingerprintManager).setIgnoreDisplayTouches(anyLong(), anyInt(), eq(false))
+ }
+
private fun createUdfpsOverlayInteractor() {
underTest = kosmos.udfpsOverlayInteractor
testScope.runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt
index 0122028..9edd62a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt
@@ -99,14 +99,14 @@
assertThat(playerModel).isNotNull()
assertThat(playerModel?.titleName).isEqualTo(TITLE)
assertThat(playerModel?.artistName).isEqualTo(ARTIST)
- assertThat(underTest.isNewPlayer(playerModel!!)).isTrue()
+ assertThat(underTest.setPlayer(playerModel!!)).isTrue()
mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)
assertThat(playerModel).isNotNull()
assertThat(playerModel?.titleName).isEqualTo(TITLE)
assertThat(playerModel?.artistName).isEqualTo(ARTIST)
- assertThat(underTest.isNewPlayer(playerModel!!)).isFalse()
+ assertThat(underTest.setPlayer(playerModel!!)).isFalse()
}
@Test
@@ -120,7 +120,7 @@
assertThat(playerModel).isNotNull()
assertThat(playerModel?.titleName).isEqualTo(TITLE)
assertThat(playerModel?.artistName).isEqualTo(ARTIST)
- assertThat(underTest.isNewPlayer(playerModel!!)).isTrue()
+ assertThat(underTest.setPlayer(playerModel!!)).isTrue()
mediaData = initMediaData(ARTIST_2, TITLE_2)
@@ -129,7 +129,7 @@
assertThat(playerModel).isNotNull()
assertThat(playerModel?.titleName).isEqualTo(TITLE_2)
assertThat(playerModel?.artistName).isEqualTo(ARTIST_2)
- assertThat(underTest.isNewPlayer(playerModel!!)).isTrue()
+ assertThat(underTest.setPlayer(playerModel!!)).isTrue()
}
private fun initMediaData(artist: String, title: String): MediaData {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
index 848c8db..9173ac9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
@@ -94,11 +94,7 @@
private val zenModeRepository = kosmos.zenModeRepository
private val tileDataInteractor =
ModesTileDataInteractor(context, kosmos.zenModeInteractor, testDispatcher)
- private val mapper =
- ModesTileMapper(
- context.resources,
- context.theme,
- )
+ private val mapper = ModesTileMapper(context.resources, context.theme)
private lateinit var userActionInteractor: ModesTileUserActionInteractor
private lateinit var secureSettings: SecureSettings
@@ -127,10 +123,7 @@
)
userActionInteractor =
- ModesTileUserActionInteractor(
- inputHandler,
- dialogDelegate,
- )
+ ModesTileUserActionInteractor(inputHandler, dialogDelegate, kosmos.zenModeInteractor)
underTest =
ModesTile(
@@ -185,7 +178,7 @@
ModesTileModel(
isActivated = true,
activeModes = listOf("One", "Two"),
- icon = TestStubDrawable().asIcon()
+ icon = TestStubDrawable().asIcon(),
)
underTest.handleUpdateState(tileState, model)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
index cd58127..88b0046 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.qs.tiles.impl.modes.domain.interactor
import android.graphics.drawable.TestStubDrawable
@@ -21,16 +23,23 @@
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.asIcon
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
+import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -43,17 +52,17 @@
@EnableFlags(android.app.Flags.FLAG_MODES_UI)
class ModesTileUserActionInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private val inputHandler = kosmos.qsTileIntentUserInputHandler
private val mockDialogDelegate = kosmos.mockModesDialogDelegate
+ private val zenModeRepository = kosmos.zenModeRepository
+ private val zenModeInteractor = kosmos.zenModeInteractor
private val underTest =
- ModesTileUserActionInteractor(
- inputHandler,
- mockDialogDelegate,
- )
+ ModesTileUserActionInteractor(inputHandler, mockDialogDelegate, zenModeInteractor)
@Test
- fun handleClick_active() = runTest {
+ fun handleClick_active_showsDialog() = runTest {
val expandable = mock<Expandable>()
underTest.handleInput(
QSTileInputTestKtx.click(data = modelOf(true, listOf("DND")), expandable = expandable)
@@ -63,7 +72,7 @@
}
@Test
- fun handleClick_inactive() = runTest {
+ fun handleClick_inactive_showsDialog() = runTest {
val expandable = mock<Expandable>()
underTest.handleInput(
QSTileInputTestKtx.click(data = modelOf(false, emptyList()), expandable = expandable)
@@ -73,7 +82,63 @@
}
@Test
- fun handleLongClick_active() = runTest {
+ @EnableFlags(Flags.FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT)
+ fun handleToggleClick_multipleModesActive_deactivatesAll() =
+ testScope.runTest {
+ val activeModes by collectLastValue(zenModeInteractor.activeModes)
+
+ zenModeRepository.addModes(
+ listOf(
+ TestModeBuilder.MANUAL_DND_ACTIVE,
+ TestModeBuilder().setName("Mode 1").setActive(true).build(),
+ TestModeBuilder().setName("Mode 2").setActive(true).build(),
+ )
+ )
+ assertThat(activeModes?.modeNames?.count()).isEqualTo(3)
+
+ underTest.handleInput(
+ QSTileInputTestKtx.toggleClick(
+ data = modelOf(true, listOf("DND", "Mode 1", "Mode 2"))
+ )
+ )
+
+ assertThat(activeModes?.isAnyActive()).isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT)
+ fun handleToggleClick_dndActive_deactivatesDnd() =
+ testScope.runTest {
+ val dndMode by collectLastValue(zenModeInteractor.dndMode)
+
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE)
+ assertThat(dndMode?.isActive).isTrue()
+
+ underTest.handleInput(
+ QSTileInputTestKtx.toggleClick(data = modelOf(true, listOf("DND")))
+ )
+
+ assertThat(dndMode?.isActive).isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT)
+ fun handleToggleClick_dndInactive_activatesDnd() =
+ testScope.runTest {
+ val dndMode by collectLastValue(zenModeInteractor.dndMode)
+
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
+ assertThat(dndMode?.isActive).isFalse()
+
+ underTest.handleInput(
+ QSTileInputTestKtx.toggleClick(data = modelOf(false, emptyList()))
+ )
+
+ assertThat(dndMode?.isActive).isTrue()
+ }
+
+ @Test
+ fun handleLongClick_active_opensSettings() = runTest {
underTest.handleInput(QSTileInputTestKtx.longClick(modelOf(true, listOf("DND"))))
QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
@@ -82,7 +147,7 @@
}
@Test
- fun handleLongClick_inactive() = runTest {
+ fun handleLongClick_inactive_opensSettings() = runTest {
underTest.handleInput(QSTileInputTestKtx.longClick(modelOf(false, emptyList())))
QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index 4e33a59..3d6882c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -251,6 +251,22 @@
}
@Test
+ fun deactivateAllModes_updatesCorrectModes() =
+ testScope.runTest {
+ zenModeRepository.addModes(
+ listOf(
+ TestModeBuilder.MANUAL_DND_ACTIVE,
+ TestModeBuilder().setName("Inactive").setActive(false).build(),
+ TestModeBuilder().setName("Active").setActive(true).build(),
+ )
+ )
+
+ underTest.deactivateAllModes()
+
+ assertThat(zenModeRepository.getModes().filter { it.isActive }).isEmpty()
+ }
+
+ @Test
fun activeModes_computesMainActiveMode() =
testScope.runTest {
val activeModes by collectLastValue(underTest.activeModes)
diff --git a/packages/SystemUI/res/drawable/ic_arrow_down_24dp.xml b/packages/SystemUI/res/drawable/ic_arrow_down_24dp.xml
new file mode 100644
index 0000000..0640116
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_arrow_down_24dp.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="#000000"
+ android:pathData="M480,616 L240,376l56,-56 184,184 184,-184 56,56 -240,240Z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_arrow_up_24dp.xml b/packages/SystemUI/res/drawable/ic_arrow_up_24dp.xml
new file mode 100644
index 0000000..65a3eef
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_arrow_up_24dp.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="#000000"
+ android:pathData="M480,432 L296,616l-56,-56 240,-240 240,240 -56,56 -184,-184Z" />
+</vector>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e417da4..fac2c4a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1812,6 +1812,7 @@
<string name="volume_ringer_change">Tap to change ringer mode</string>
<string name="volume_ringer_mode">ringer mode</string>
+ <string name="volume_ringer_drawer_closed_content_description"><xliff:g id="volume ringer status" example="Ring">%1$s</xliff:g>, tap to change ringer mode </string>
<!-- Hint for accessibility. For example: double tap to mute [CHAR_LIMIT=NONE] -->
<string name="volume_ringer_hint_mute">mute</string>
@@ -3949,6 +3950,8 @@
<string name="tutorial_action_key_success_title">Well done!</string>
<!-- Text shown to the user after they complete action key tutorial [CHAR LIMIT=NONE] -->
<string name="tutorial_action_key_success_body">You completed the view all apps gesture</string>
+ <!-- Content description for the animation playing during the tutorial. The user can click the animation to pause and unpause playback. [CHAR LIMIT=NONE] -->
+ <string name="tutorial_animation_content_description">Tutorial animation, click to pause and resume play.</string>
<!-- Content description for keyboard backlight brightness dialog [CHAR LIMIT=NONE] -->
<string name="keyboard_backlight_dialog_title">Keyboard backlight</string>
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 1f21af8..ad12229 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -55,7 +55,6 @@
import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.HapClientProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.systemui.accessibility.hearingaid.HearingDevicesListAdapter.HearingDeviceItemCallback;
@@ -67,7 +66,6 @@
import com.android.systemui.bluetooth.qsdialog.DeviceItemFactory;
import com.android.systemui.bluetooth.qsdialog.DeviceItemType;
import com.android.systemui.bluetooth.qsdialog.SavedHearingDeviceItemFactory;
-import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.res.R;
@@ -87,6 +85,7 @@
*/
public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
HearingDeviceItemCallback, BluetoothCallback {
+
private static final String TAG = "HearingDevicesDialogDelegate";
@VisibleForTesting
static final String ACTION_BLUETOOTH_DEVICE_DETAILS =
@@ -96,25 +95,27 @@
@VisibleForTesting
static final Intent LIVE_CAPTION_INTENT = new Intent(
"com.android.settings.action.live_caption");
+
private final SystemUIDialog.Factory mSystemUIDialogFactory;
private final DialogTransitionAnimator mDialogTransitionAnimator;
private final ActivityStarter mActivityStarter;
- private final boolean mShowPairNewDevice;
private final LocalBluetoothManager mLocalBluetoothManager;
private final Handler mMainHandler;
private final AudioManager mAudioManager;
private final LocalBluetoothProfileManager mProfileManager;
- private final HapClientProfile mHapClientProfile;
private final HearingDevicesUiEventLogger mUiEventLogger;
+ private final boolean mShowPairNewDevice;
private final int mLaunchSourceId;
- private HearingDevicesListAdapter mDeviceListAdapter;
- private HearingDevicesPresetsController mPresetsController;
- private Context mApplicationContext;
+
private SystemUIDialog mDialog;
+
private RecyclerView mDeviceList;
private List<DeviceItem> mHearingDeviceItemList;
+ private HearingDevicesListAdapter mDeviceListAdapter;
+
private View mPresetLayout;
private Spinner mPresetSpinner;
+ private HearingDevicesPresetsController mPresetController;
private HearingDevicesSpinnerAdapter mPresetInfoAdapter;
private final HearingDevicesPresetsController.PresetCallback mPresetCallback =
new HearingDevicesPresetsController.PresetCallback() {
@@ -122,20 +123,18 @@
public void onPresetInfoUpdated(List<BluetoothHapPresetInfo> presetInfos,
int activePresetIndex) {
mMainHandler.post(
- () -> refreshPresetInfoAdapter(presetInfos, activePresetIndex));
+ () -> refreshPresetUi(presetInfos, activePresetIndex));
}
@Override
public void onPresetCommandFailed(int reason) {
- final List<BluetoothHapPresetInfo> presetInfos =
- mPresetsController.getAllPresetInfo();
- final int activePresetIndex = mPresetsController.getActivePresetIndex();
+ mPresetController.refreshPresetInfo();
mMainHandler.post(() -> {
- refreshPresetInfoAdapter(presetInfos, activePresetIndex);
- showPresetErrorToast(mApplicationContext);
+ showErrorToast(R.string.hearing_devices_presets_error);
});
}
};
+
private final List<DeviceItemFactory> mHearingDeviceItemFactoryList = List.of(
new ActiveHearingDeviceItemFactory(),
new AvailableHearingDeviceItemFactory(),
@@ -159,7 +158,6 @@
@AssistedInject
public HearingDevicesDialogDelegate(
- @Application Context applicationContext,
@Assisted boolean showPairNewDevice,
@Assisted @HearingDevicesUiEventLogger.LaunchSourceId int launchSourceId,
SystemUIDialog.Factory systemUIDialogFactory,
@@ -169,7 +167,6 @@
@Main Handler handler,
AudioManager audioManager,
HearingDevicesUiEventLogger uiEventLogger) {
- mApplicationContext = applicationContext;
mShowPairNewDevice = showPairNewDevice;
mSystemUIDialogFactory = systemUIDialogFactory;
mActivityStarter = activityStarter;
@@ -178,7 +175,6 @@
mMainHandler = handler;
mAudioManager = audioManager;
mProfileManager = localBluetoothManager.getProfileManager();
- mHapClientProfile = mProfileManager.getHapClientProfile();
mUiEventLogger = uiEventLogger;
mLaunchSourceId = launchSourceId;
}
@@ -229,38 +225,26 @@
@Override
public void onActiveDeviceChanged(@Nullable CachedBluetoothDevice activeDevice,
int bluetoothProfile) {
- CachedBluetoothDevice activeHearingDevice;
- mHearingDeviceItemList = getHearingDevicesList();
- if (mPresetsController != null) {
- activeHearingDevice = getActiveHearingDevice(mHearingDeviceItemList);
- mPresetsController.setHearingDeviceIfSupportHap(activeHearingDevice);
- } else {
- activeHearingDevice = null;
+ refreshDeviceUi();
+ if (mPresetController != null) {
+ mPresetController.setDevice(getActiveHearingDevice());
+ mMainHandler.post(() -> {
+ mPresetLayout.setVisibility(
+ mPresetController.isPresetControlAvailable() ? VISIBLE : GONE);
+ });
}
- mMainHandler.post(() -> {
- mDeviceListAdapter.refreshDeviceItemList(mHearingDeviceItemList);
- final List<BluetoothHapPresetInfo> presetInfos =
- mPresetsController.getAllPresetInfo();
- final int activePresetIndex = mPresetsController.getActivePresetIndex();
- refreshPresetInfoAdapter(presetInfos, activePresetIndex);
- mPresetLayout.setVisibility(
- (activeHearingDevice != null && activeHearingDevice.isConnectedHapClientDevice()
- && !mPresetInfoAdapter.isEmpty()) ? VISIBLE : GONE);
- });
}
@Override
public void onProfileConnectionStateChanged(@NonNull CachedBluetoothDevice cachedDevice,
int state, int bluetoothProfile) {
- mHearingDeviceItemList = getHearingDevicesList();
- mMainHandler.post(() -> mDeviceListAdapter.refreshDeviceItemList(mHearingDeviceItemList));
+ refreshDeviceUi();
}
@Override
public void onAclConnectionStateChanged(@NonNull CachedBluetoothDevice cachedDevice,
int state) {
- mHearingDeviceItemList = getHearingDevicesList();
- mMainHandler.post(() -> mDeviceListAdapter.refreshDeviceItemList(mHearingDeviceItemList));
+ refreshDeviceUi();
}
@Override
@@ -306,13 +290,9 @@
if (mLocalBluetoothManager == null) {
return;
}
-
mLocalBluetoothManager.getEventManager().registerCallback(this);
- if (mPresetsController != null) {
- mPresetsController.registerHapCallback();
- if (mHapClientProfile != null && !mHapClientProfile.isProfileReady()) {
- mProfileManager.addServiceListener(mPresetsController);
- }
+ if (mPresetController != null) {
+ mPresetController.registerHapCallback();
}
}
@@ -322,37 +302,25 @@
return;
}
- if (mPresetsController != null) {
- mPresetsController.unregisterHapCallback();
- mProfileManager.removeServiceListener(mPresetsController);
+ if (mPresetController != null) {
+ mPresetController.unregisterHapCallback();
}
mLocalBluetoothManager.getEventManager().unregisterCallback(this);
}
- @VisibleForTesting
- void setHearingDevicesPresetsController(HearingDevicesPresetsController controller) {
- mPresetsController = controller;
- }
-
private void setupDeviceListView(SystemUIDialog dialog) {
mDeviceList.setLayoutManager(new LinearLayoutManager(dialog.getContext()));
- mHearingDeviceItemList = getHearingDevicesList();
+ mHearingDeviceItemList = getHearingDeviceItemList();
mDeviceListAdapter = new HearingDevicesListAdapter(mHearingDeviceItemList, this);
mDeviceList.setAdapter(mDeviceListAdapter);
}
private void setupPresetSpinner(SystemUIDialog dialog) {
- if (mPresetsController == null) {
- mPresetsController = new HearingDevicesPresetsController(mProfileManager,
- mPresetCallback);
- }
- final CachedBluetoothDevice activeHearingDevice = getActiveHearingDevice(
- mHearingDeviceItemList);
- mPresetsController.setHearingDeviceIfSupportHap(activeHearingDevice);
+ mPresetController = new HearingDevicesPresetsController(mProfileManager, mPresetCallback);
+ mPresetController.setDevice(getActiveHearingDevice());
mPresetInfoAdapter = new HearingDevicesSpinnerAdapter(dialog.getContext());
mPresetSpinner.setAdapter(mPresetInfoAdapter);
-
// disable redundant Touch & Hold accessibility action for Switch Access
mPresetSpinner.setAccessibilityDelegate(new View.AccessibilityDelegate() {
@Override
@@ -362,20 +330,18 @@
super.onInitializeAccessibilityNodeInfo(host, info);
}
});
-
- // Refresh the spinner and setSelection(index, false) before setOnItemSelectedListener() to
- // avoid extra onItemSelected() get called when first register the listener.
- final List<BluetoothHapPresetInfo> presetInfos = mPresetsController.getAllPresetInfo();
- final int activePresetIndex = mPresetsController.getActivePresetIndex();
- refreshPresetInfoAdapter(presetInfos, activePresetIndex);
+ // Should call setSelection(index, false) for the spinner before setOnItemSelectedListener()
+ // to avoid extra onItemSelected() get called when first register the listener.
+ refreshPresetUi(mPresetController.getAllPresetInfo(),
+ mPresetController.getActivePresetIndex());
mPresetSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mPresetInfoAdapter.setSelected(position);
mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PRESET_SELECT,
mLaunchSourceId);
- mPresetsController.selectPreset(
- mPresetsController.getAllPresetInfo().get(position).getIndex());
+ mPresetController.selectPreset(
+ mPresetController.getAllPresetInfo().get(position).getIndex());
}
@Override
@@ -383,9 +349,8 @@
// Do nothing
}
});
- mPresetLayout.setVisibility(
- (activeHearingDevice != null && activeHearingDevice.isConnectedHapClientDevice()
- && !mPresetInfoAdapter.isEmpty()) ? VISIBLE : GONE);
+
+ mPresetLayout.setVisibility(mPresetController.isPresetControlAvailable() ? VISIBLE : GONE);
}
private void setupPairNewDeviceButton(SystemUIDialog dialog) {
@@ -405,13 +370,12 @@
}
private void setupRelatedToolsView(SystemUIDialog dialog) {
-
final Context context = dialog.getContext();
final List<ToolItem> toolItemList = new ArrayList<>();
final String[] toolNameArray;
final String[] toolIconArray;
- ToolItem preInstalledItem = getLiveCaption(context);
+ ToolItem preInstalledItem = getLiveCaptionToolItem(context);
if (preInstalledItem != null) {
toolItemList.add(preInstalledItem);
}
@@ -432,7 +396,7 @@
final LinearLayout toolsContainer = dialog.requireViewById(R.id.tools_container);
for (int i = 0; i < toolItemList.size(); i++) {
- View view = createHearingToolView(context, toolItemList.get(i), toolsContainer);
+ View view = createToolView(context, toolItemList.get(i), toolsContainer);
toolsContainer.addView(view);
if (i != toolItemList.size() - 1) {
final int spaceSize = context.getResources().getDimensionPixelSize(
@@ -444,8 +408,14 @@
}
}
- private void refreshPresetInfoAdapter(List<BluetoothHapPresetInfo> presetInfos,
- int activePresetIndex) {
+ private void refreshDeviceUi() {
+ mHearingDeviceItemList = getHearingDeviceItemList();
+ mMainHandler.post(() -> {
+ mDeviceListAdapter.refreshDeviceItemList(mHearingDeviceItemList);
+ });
+ }
+
+ private void refreshPresetUi(List<BluetoothHapPresetInfo> presetInfos, int activePresetIndex) {
mPresetInfoAdapter.clear();
mPresetInfoAdapter.addAll(
presetInfos.stream().map(BluetoothHapPresetInfo::getName).toList());
@@ -460,12 +430,11 @@
}
}
- private List<DeviceItem> getHearingDevicesList() {
+ private List<DeviceItem> getHearingDeviceItemList() {
if (mLocalBluetoothManager == null
|| !mLocalBluetoothManager.getBluetoothAdapter().isEnabled()) {
return emptyList();
}
-
return mLocalBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy().stream()
.map(this::createHearingDeviceItem)
.filter(Objects::nonNull)
@@ -473,8 +442,8 @@
}
@Nullable
- private CachedBluetoothDevice getActiveHearingDevice(List<DeviceItem> hearingDeviceItemList) {
- return hearingDeviceItemList.stream()
+ private CachedBluetoothDevice getActiveHearingDevice() {
+ return mHearingDeviceItemList.stream()
.filter(item -> item.getType() == DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE)
.map(DeviceItem::getCachedBluetoothDevice)
.findFirst()
@@ -495,7 +464,7 @@
}
@NonNull
- private View createHearingToolView(Context context, ToolItem item, ViewGroup container) {
+ private View createToolView(Context context, ToolItem item, ViewGroup container) {
View view = LayoutInflater.from(context).inflate(R.layout.hearing_tool_item, container,
false);
ImageView icon = view.requireViewById(R.id.tool_icon);
@@ -522,7 +491,7 @@
return view;
}
- private ToolItem getLiveCaption(Context context) {
+ private ToolItem getLiveCaptionToolItem(Context context) {
final PackageManager packageManager = context.getPackageManager();
LIVE_CAPTION_INTENT.setPackage(packageManager.getSystemCaptionsServicePackageName());
final List<ResolveInfo> resolved = packageManager.queryIntentActivities(LIVE_CAPTION_INTENT,
@@ -534,7 +503,6 @@
LIVE_CAPTION_INTENT,
/* isCustomIcon= */ true);
}
-
return null;
}
@@ -544,7 +512,7 @@
}
}
- private void showPresetErrorToast(Context context) {
- Toast.makeText(context, R.string.hearing_devices_presets_error, Toast.LENGTH_SHORT).show();
+ private void showErrorToast(int stringResId) {
+ Toast.makeText(mDialog.getContext(), stringResId, Toast.LENGTH_SHORT).show();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java
index aa95fd0..e109108 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java
@@ -25,16 +25,18 @@
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HapClientProfile;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.utils.ThreadUtils;
+import java.util.ArrayList;
import java.util.List;
/**
- * The controller of the hearing devices presets of the bluetooth Hearing Access Profile.
+ * The controller of handling hearing device preset with Bluetooth Hearing Access Profile(HAP).
*/
public class HearingDevicesPresetsController implements
LocalBluetoothProfileManager.ServiceListener, BluetoothHapClient.Callback {
@@ -46,11 +48,13 @@
private final HapClientProfile mHapClientProfile;
private final PresetCallback mPresetCallback;
- private CachedBluetoothDevice mActiveHearingDevice;
+ private CachedBluetoothDevice mDevice;
+ private List<BluetoothHapPresetInfo> mPresetInfos = new ArrayList<>();
+ private int mActivePresetIndex = BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
private int mSelectedPresetIndex;
- public HearingDevicesPresetsController(LocalBluetoothProfileManager profileManager,
- PresetCallback presetCallback) {
+ public HearingDevicesPresetsController(@NonNull LocalBluetoothProfileManager profileManager,
+ @Nullable PresetCallback presetCallback) {
mProfileManager = profileManager;
mHapClientProfile = mProfileManager.getHapClientProfile();
mPresetCallback = presetCallback;
@@ -61,7 +65,7 @@
if (mHapClientProfile != null && mHapClientProfile.isProfileReady()) {
mProfileManager.removeServiceListener(this);
registerHapCallback();
- mPresetCallback.onPresetInfoUpdated(getAllPresetInfo(), getActivePresetIndex());
+ refreshPresetInfo();
}
}
@@ -72,51 +76,53 @@
@Override
public void onPresetSelected(@NonNull BluetoothDevice device, int presetIndex, int reason) {
- if (mActiveHearingDevice == null) {
+ if (mDevice == null) {
return;
}
- if (device.equals(mActiveHearingDevice.getDevice())) {
+ if (device.equals(mDevice.getDevice())) {
if (DEBUG) {
Log.d(TAG, "onPresetSelected, device: " + device.getAddress()
+ ", presetIndex: " + presetIndex + ", reason: " + reason);
}
- mPresetCallback.onPresetInfoUpdated(getAllPresetInfo(), getActivePresetIndex());
+ refreshPresetInfo();
}
}
@Override
public void onPresetInfoChanged(@NonNull BluetoothDevice device,
@NonNull List<BluetoothHapPresetInfo> presetInfoList, int reason) {
- if (mActiveHearingDevice == null) {
+ if (mDevice == null) {
return;
}
- if (device.equals(mActiveHearingDevice.getDevice())) {
+ if (device.equals(mDevice.getDevice())) {
if (DEBUG) {
Log.d(TAG, "onPresetInfoChanged, device: " + device.getAddress()
+ ", reason: " + reason + ", infoList: " + presetInfoList);
}
- mPresetCallback.onPresetInfoUpdated(getAllPresetInfo(), getActivePresetIndex());
+ refreshPresetInfo();
}
}
@Override
public void onPresetSelectionFailed(@NonNull BluetoothDevice device, int reason) {
- if (mActiveHearingDevice == null) {
+ if (mDevice == null) {
return;
}
- if (device.equals(mActiveHearingDevice.getDevice())) {
+ if (device.equals(mDevice.getDevice())) {
Log.w(TAG, "onPresetSelectionFailed, device: " + device.getAddress()
+ ", reason: " + reason);
- mPresetCallback.onPresetCommandFailed(reason);
+ if (mPresetCallback != null) {
+ mPresetCallback.onPresetCommandFailed(reason);
+ }
}
}
@Override
public void onPresetSelectionForGroupFailed(int hapGroupId, int reason) {
- if (mActiveHearingDevice == null || mHapClientProfile == null) {
+ if (mDevice == null || mHapClientProfile == null) {
return;
}
- if (hapGroupId == mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice())) {
+ if (hapGroupId == mHapClientProfile.getHapGroup(mDevice.getDevice())) {
Log.w(TAG, "onPresetSelectionForGroupFailed, group: " + hapGroupId
+ ", reason: " + reason);
selectPresetIndependently(mSelectedPresetIndex);
@@ -125,33 +131,43 @@
@Override
public void onSetPresetNameFailed(@NonNull BluetoothDevice device, int reason) {
- if (mActiveHearingDevice == null) {
+ if (mDevice == null) {
return;
}
- if (device.equals(mActiveHearingDevice.getDevice())) {
+ if (device.equals(mDevice.getDevice())) {
Log.w(TAG, "onSetPresetNameFailed, device: " + device.getAddress()
+ ", reason: " + reason);
- mPresetCallback.onPresetCommandFailed(reason);
+ if (mPresetCallback != null) {
+ mPresetCallback.onPresetCommandFailed(reason);
+ }
}
}
@Override
public void onSetPresetNameForGroupFailed(int hapGroupId, int reason) {
- if (mActiveHearingDevice == null || mHapClientProfile == null) {
+ if (mDevice == null || mHapClientProfile == null) {
return;
}
- if (hapGroupId == mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice())) {
+ if (hapGroupId == mHapClientProfile.getHapGroup(mDevice.getDevice())) {
Log.w(TAG, "onSetPresetNameForGroupFailed, group: " + hapGroupId
+ ", reason: " + reason);
}
- mPresetCallback.onPresetCommandFailed(reason);
+ if (mPresetCallback != null) {
+ mPresetCallback.onPresetCommandFailed(reason);
+ }
}
/**
- * Registers a callback to be notified about operation changed for {@link HapClientProfile}.
+ * Registers a callback to be notified about operation changed of {@link HapClientProfile}.
*/
public void registerHapCallback() {
if (mHapClientProfile != null) {
+ if (!mHapClientProfile.isProfileReady()) {
+ mProfileManager.addServiceListener(this);
+ Log.w(TAG, "Profile is not ready yet, the callback will be registered once the "
+ + "profile is ready.");
+ return;
+ }
try {
mHapClientProfile.registerCallback(ThreadUtils.getBackgroundExecutor(), this);
} catch (IllegalArgumentException e) {
@@ -163,9 +179,10 @@
}
/**
- * Removes a previously-added {@link HapClientProfile} callback.
+ * Removes a previously-added {@link HapClientProfile} callback if exist.
*/
public void unregisterHapCallback() {
+ mProfileManager.removeServiceListener(this);
if (mHapClientProfile != null) {
try {
mHapClientProfile.unregisterCallback(this);
@@ -177,39 +194,94 @@
}
/**
- * Sets the hearing device for this controller to control the preset if it supports
- * {@link HapClientProfile}.
+ * Sets the device for this controller to control the preset if it supports
+ * {@link HapClientProfile}, otherwise the device of this controller will be {@code null}.
*
- * @param activeHearingDevice the {@link CachedBluetoothDevice} need to be hearing aid device
- * and support {@link HapClientProfile}.
+ * @param device the {@link CachedBluetoothDevice} set to the controller
*/
- public void setHearingDeviceIfSupportHap(CachedBluetoothDevice activeHearingDevice) {
- if (mHapClientProfile == null || activeHearingDevice == null) {
- mActiveHearingDevice = null;
- return;
- }
- if (activeHearingDevice.getProfiles().stream().anyMatch(
+ public void setDevice(@Nullable CachedBluetoothDevice device) {
+ if (device != null && device.getProfiles().stream().anyMatch(
profile -> profile instanceof HapClientProfile)) {
- mActiveHearingDevice = activeHearingDevice;
+ mDevice = device;
} else {
- mActiveHearingDevice = null;
+ mDevice = null;
+ }
+ refreshPresetInfo();
+ }
+
+ /**
+ * Refreshes the preset info of {@code mDevice}. If the preset info list or the active preset
+ * index is updated, the {@link PresetCallback#onPresetInfoUpdated(List, int)} will be called
+ * to notify the change.
+ *
+ * <b>Note:</b> If {@code mDevice} is null, the cached preset info and active preset index will
+ * be reset to empty list and {@code BluetoothHapClient.PRESET_INDEX_UNAVAILABLE} respectively.
+ */
+ public void refreshPresetInfo() {
+ List<BluetoothHapPresetInfo> updatedInfos = new ArrayList<>();
+ int updatedActiveIndex = BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
+ if (mHapClientProfile != null && mDevice != null) {
+ updatedInfos = mHapClientProfile.getAllPresetInfo(mDevice.getDevice()).stream().filter(
+ BluetoothHapPresetInfo::isAvailable).toList();
+ updatedActiveIndex = mHapClientProfile.getActivePresetIndex(mDevice.getDevice());
+ }
+ final boolean infoUpdated = !mPresetInfos.equals(updatedInfos);
+ final boolean activeIndexUpdated = mActivePresetIndex != updatedActiveIndex;
+ mPresetInfos = updatedInfos;
+ mActivePresetIndex = updatedActiveIndex;
+ if (infoUpdated || activeIndexUpdated) {
+ if (mPresetCallback != null) {
+ mPresetCallback.onPresetInfoUpdated(mPresetInfos, mActivePresetIndex);
+ }
}
}
/**
- * Selects the currently active preset for {@code mActiveHearingDevice} individual device or
- * the device group according to whether it supports synchronized presets or not.
+ * @return if the preset control is available. The preset control is available only
+ * when the {@code mDevice} supports HAP and the retrieved preset info list is not empty.
+ */
+ public boolean isPresetControlAvailable() {
+ boolean deviceValid = mDevice != null && mDevice.isConnectedHapClientDevice();
+ boolean hasPreset = mPresetInfos != null && !mPresetInfos.isEmpty();
+ return deviceValid && hasPreset;
+ }
+
+ /**
+ * @return a list of {@link BluetoothHapPresetInfo} retrieved from {@code mDevice}
+ */
+ public List<BluetoothHapPresetInfo> getAllPresetInfo() {
+ if (mDevice == null || mHapClientProfile == null) {
+ return emptyList();
+ }
+ return mPresetInfos;
+ }
+
+ /**
+ * Gets the currently active preset of {@code mDevice}.
+ *
+ * @return active preset index
+ */
+ public int getActivePresetIndex() {
+ if (mDevice == null || mHapClientProfile == null) {
+ return BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
+ }
+ return mActivePresetIndex;
+ }
+
+ /**
+ * Selects the preset for {@code mDevice}. Performs individual or group operation according
+ * to whether the device supports synchronized presets feature or not.
*
* @param presetIndex an index of one of the available presets
*/
public void selectPreset(int presetIndex) {
- if (mActiveHearingDevice == null || mHapClientProfile == null) {
+ if (mDevice == null || mHapClientProfile == null) {
return;
}
mSelectedPresetIndex = presetIndex;
boolean supportSynchronizedPresets = mHapClientProfile.supportsSynchronizedPresets(
- mActiveHearingDevice.getDevice());
- int hapGroupId = mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice());
+ mDevice.getDevice());
+ int hapGroupId = mHapClientProfile.getHapGroup(mDevice.getDevice());
if (supportSynchronizedPresets) {
if (hapGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
selectPresetSynchronously(hapGroupId, presetIndex);
@@ -222,63 +294,37 @@
}
}
- /**
- * Gets all preset info for {@code mActiveHearingDevice} device.
- *
- * @return a list of all known preset info
- */
- public List<BluetoothHapPresetInfo> getAllPresetInfo() {
- if (mActiveHearingDevice == null || mHapClientProfile == null) {
- return emptyList();
- }
- return mHapClientProfile.getAllPresetInfo(mActiveHearingDevice.getDevice()).stream().filter(
- BluetoothHapPresetInfo::isAvailable).toList();
- }
-
- /**
- * Gets the currently active preset for {@code mActiveHearingDevice} device.
- *
- * @return active preset index
- */
- public int getActivePresetIndex() {
- if (mActiveHearingDevice == null || mHapClientProfile == null) {
- return BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
- }
- return mHapClientProfile.getActivePresetIndex(mActiveHearingDevice.getDevice());
- }
-
private void selectPresetSynchronously(int groupId, int presetIndex) {
- if (mActiveHearingDevice == null || mHapClientProfile == null) {
+ if (mDevice == null || mHapClientProfile == null) {
return;
}
if (DEBUG) {
Log.d(TAG, "selectPresetSynchronously"
+ ", presetIndex: " + presetIndex
+ ", groupId: " + groupId
- + ", device: " + mActiveHearingDevice.getAddress());
+ + ", device: " + mDevice.getAddress());
}
mHapClientProfile.selectPresetForGroup(groupId, presetIndex);
}
private void selectPresetIndependently(int presetIndex) {
- if (mActiveHearingDevice == null || mHapClientProfile == null) {
+ if (mDevice == null || mHapClientProfile == null) {
return;
}
if (DEBUG) {
Log.d(TAG, "selectPresetIndependently"
+ ", presetIndex: " + presetIndex
- + ", device: " + mActiveHearingDevice.getAddress());
+ + ", device: " + mDevice.getAddress());
}
- mHapClientProfile.selectPreset(mActiveHearingDevice.getDevice(), presetIndex);
- final CachedBluetoothDevice subDevice = mActiveHearingDevice.getSubDevice();
+ mHapClientProfile.selectPreset(mDevice.getDevice(), presetIndex);
+ final CachedBluetoothDevice subDevice = mDevice.getSubDevice();
if (subDevice != null) {
if (DEBUG) {
Log.d(TAG, "selectPreset for subDevice, device: " + subDevice);
}
mHapClientProfile.selectPreset(subDevice.getDevice(), presetIndex);
}
- for (final CachedBluetoothDevice memberDevice :
- mActiveHearingDevice.getMemberDevice()) {
+ for (final CachedBluetoothDevice memberDevice : mDevice.getMemberDevice()) {
if (DEBUG) {
Log.d(TAG, "selectPreset for memberDevice, device: " + memberDevice);
}
@@ -294,9 +340,8 @@
/**
* Called when preset info from {@link HapClientProfile} operation get updated.
*
- * @param presetInfos all preset info for {@code mActiveHearingDevice} device
- * @param activePresetIndex currently active preset index for {@code mActiveHearingDevice}
- * device
+ * @param presetInfos all preset info of {@code mDevice}
+ * @param activePresetIndex currently active preset index of {@code mDevice}
*/
void onPresetInfoUpdated(List<BluetoothHapPresetInfo> presetInfos, int activePresetIndex);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index f6b6655..b653711 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -27,7 +27,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AlertDialog;
-import android.app.KeyguardManager;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
@@ -320,16 +319,6 @@
mBiometricCallback = new BiometricCallback();
mMSDLPlayer = msdlPlayer;
- // Listener for when device locks from adaptive auth, dismiss prompt
- getContext().getSystemService(KeyguardManager.class).addKeyguardLockedStateListener(
- getContext().getMainExecutor(),
- isKeyguardLocked -> {
- if (isKeyguardLocked) {
- onStartedGoingToSleep();
- }
- }
- );
-
final BiometricModalities biometricModalities = new BiometricModalities(
Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds));
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 4faf6ff..316849d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -27,6 +27,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityTaskManager;
+import android.app.KeyguardManager;
import android.app.TaskStackListener;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -737,6 +738,7 @@
@Background DelayableExecutor bgExecutor,
@NonNull UdfpsUtils udfpsUtils,
@NonNull VibratorHelper vibratorHelper,
+ @NonNull KeyguardManager keyguardManager,
Lazy<ViewCapture> daggerLazyViewCapture,
@NonNull MSDLPlayer msdlPlayer) {
mContext = context;
@@ -768,6 +770,15 @@
mPromptViewModelProvider = promptViewModelProvider;
mCredentialViewModelProvider = credentialViewModelProvider;
+ keyguardManager.addKeyguardLockedStateListener(
+ context.getMainExecutor(),
+ isKeyguardLocked -> {
+ if (isKeyguardLocked) {
+ closeDialog("Device lock");
+ }
+ }
+ );
+
mOrientationListener = new BiometricDisplayListener(
context,
mDisplayManager,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
index 9763295..8a5e011 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
@@ -29,6 +29,7 @@
import com.android.systemui.res.R
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import javax.inject.Inject
+import kotlin.math.max
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
@@ -39,7 +40,6 @@
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
-import kotlin.math.max
/** Encapsulates business logic for interacting with the UDFPS overlay. */
@SysUISingleton
@@ -55,10 +55,7 @@
private fun calculateIconSize(): Int {
val pixelPitch = context.resources.getFloat(R.dimen.pixel_pitch)
if (pixelPitch <= 0) {
- Log.e(
- "UdfpsOverlayInteractor",
- "invalid pixelPitch: $pixelPitch. Pixel pitch must be updated per device.",
- )
+ Log.e(TAG, "invalid pixelPitch: $pixelPitch. Pixel pitch must be updated per device.")
}
return (context.resources.getFloat(R.dimen.udfps_icon_size) / pixelPitch).toInt()
}
@@ -84,13 +81,15 @@
}
/** Sets whether Udfps overlay should handle touches */
- fun setHandleTouches(shouldHandle: Boolean = true) {
- if (authController.isUdfpsSupported && shouldHandle != _shouldHandleTouches.value) {
+ fun setHandleTouches(shouldHandle: Boolean) {
+ if (authController.isUdfpsSupported) {
fingerprintManager?.setIgnoreDisplayTouches(
requestId.value,
authController.udfpsProps!!.get(0).sensorId,
!shouldHandle,
)
+ } else {
+ Log.d(TAG, "setIgnoreDisplayTouches not set, UDFPS not supported")
}
_shouldHandleTouches.value = shouldHandle
}
@@ -123,12 +122,14 @@
// Padding between the fingerprint icon and its bounding box in pixels.
val iconPadding: Flow<Int> =
- udfpsOverlayParams.map { params ->
- val sensorWidth = params.nativeSensorBounds.right - params.nativeSensorBounds.left
- val nativePadding = (sensorWidth - iconSize) / 2
- // padding can be negative when udfpsOverlayParams has not been initialized yet.
- max(0, (nativePadding * params.scaleFactor).toInt())
- }.distinctUntilChanged()
+ udfpsOverlayParams
+ .map { params ->
+ val sensorWidth = params.nativeSensorBounds.right - params.nativeSensorBounds.left
+ val nativePadding = (sensorWidth - iconSize) / 2
+ // padding can be negative when udfpsOverlayParams has not been initialized yet.
+ max(0, (nativePadding * params.scaleFactor).toInt())
+ }
+ .distinctUntilChanged()
companion object {
private const val TAG = "UdfpsOverlayInteractor"
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
index 1b044de..0c1bc83 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
@@ -20,6 +20,7 @@
import androidx.annotation.RawRes
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
+import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -33,8 +34,12 @@
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalConfiguration
@@ -125,6 +130,8 @@
config: TutorialScreenConfig,
modifier: Modifier = Modifier,
) {
+ val focusRequester = remember { FocusRequester() }
+ LaunchedEffect(Unit) { focusRequester.requestFocus() }
val (titleTextId, bodyTextId) =
if (actionState is Finished) {
config.strings.titleSuccessResId to config.strings.bodySuccessResId
@@ -136,6 +143,7 @@
text = stringResource(id = titleTextId),
style = MaterialTheme.typography.displayLarge,
color = config.colors.title,
+ modifier = Modifier.focusRequester(focusRequester).focusable(),
)
Spacer(modifier = Modifier.height(16.dp))
Text(
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
index abd39cc..ad18817 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
@@ -36,6 +36,9 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.node.Ref
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.util.lerp
import com.airbnb.lottie.LottieComposition
import com.airbnb.lottie.compose.LottieAnimation
@@ -47,6 +50,7 @@
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted
+import com.android.systemui.res.R
@Composable
fun TutorialAnimation(
@@ -104,11 +108,15 @@
isPlaying = isPlaying,
restartOnPlay = false,
)
+ val animationDescription = stringResource(R.string.tutorial_animation_content_description)
LottieAnimation(
composition = composition,
progress = { progress },
dynamicProperties = animationProperties,
- modifier = Modifier.fillMaxSize().clickable { isPlaying = !isPlaying },
+ modifier =
+ Modifier.fillMaxSize()
+ .clickable { isPlaying = !isPlaying }
+ .semantics { contentDescription = animationDescription },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
index d1f9fa2..e8d3bfa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
@@ -27,7 +27,6 @@
import android.service.notification.ZenModeConfig
import android.util.Log
import com.android.settingslib.notification.modes.EnableZenModeDialog
-import com.android.settingslib.notification.modes.ZenMode
import com.android.settingslib.notification.modes.ZenModeDialogMetricsLogger
import com.android.systemui.animation.Expandable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -99,15 +98,6 @@
private var oldIsAvailable = false
private var settingsValue: Int = 0
- private val dndMode: StateFlow<ZenMode?> by lazy {
- ModesUi.assertInNewMode()
- interactor.dndMode.stateIn(
- scope = backgroundScope,
- started = SharingStarted.Eagerly,
- initialValue = null,
- )
- }
-
private val isAvailable: StateFlow<Boolean> by lazy {
ModesUi.assertInNewMode()
interactor.isZenAvailable.stateIn(
@@ -146,7 +136,7 @@
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
if (ModesUi.isEnabled) {
- combine(isAvailable, dndMode) { isAvailable, dndMode ->
+ combine(isAvailable, interactor.dndMode) { isAvailable, dndMode ->
if (!isAvailable) {
KeyguardQuickAffordanceConfig.LockScreenState.Hidden
} else if (dndMode?.isActive == true) {
@@ -222,7 +212,7 @@
if (!isAvailable.value) {
KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
} else {
- val dnd = dndMode.value
+ val dnd = interactor.dndMode.value
if (dnd == null) {
Log.wtf(TAG, "Triggered DND but it's null!?")
return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
index 341b8d8..1b39d55 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
@@ -30,14 +30,10 @@
import com.android.systemui.media.controls.util.MediaSmartspaceLogger.Companion.SMARTSPACE_CARD_DISMISS_EVENT
import com.android.systemui.media.controls.util.MediaSmartspaceLogger.Companion.SMARTSPACE_CARD_SEEN_EVENT
import com.android.systemui.media.controls.util.SmallHash
-import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.time.SystemClock
-import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import java.util.Locale
import java.util.TreeMap
import javax.inject.Inject
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -49,37 +45,9 @@
constructor(
@Application private val applicationContext: Context,
private val systemClock: SystemClock,
- private val configurationController: ConfigurationController,
private val smartspaceLogger: MediaSmartspaceLogger,
) {
- val onAnyMediaConfigurationChange: Flow<Unit> = conflatedCallbackFlow {
- val callback =
- object : ConfigurationController.ConfigurationListener {
- override fun onDensityOrFontScaleChanged() {
- trySend(Unit)
- }
-
- override fun onThemeChanged() {
- trySend(Unit)
- }
-
- override fun onUiModeChanged() {
- trySend(Unit)
- }
-
- override fun onLocaleListChanged() {
- if (locale != applicationContext.resources.configuration.locales.get(0)) {
- locale = applicationContext.resources.configuration.locales.get(0)
- trySend(Unit)
- }
- }
- }
- configurationController.addCallback(callback)
- trySend(Unit)
- awaitClose { configurationController.removeCallback(callback) }
- }
-
/** Instance id of media control that recommendations card reactivated. */
private val _reactivatedId: MutableStateFlow<InstanceId?> = MutableStateFlow(null)
val reactivatedId: StateFlow<InstanceId?> = _reactivatedId.asStateFlow()
@@ -190,7 +158,7 @@
fun addMediaDataLoadingState(
mediaDataLoadingModel: MediaDataLoadingModel,
- isUpdate: Boolean = true
+ isUpdate: Boolean = true,
) {
val sortedMap = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator)
sortedMap.putAll(
@@ -395,7 +363,7 @@
logSmarspaceRecommendationCardUserEvent(
SMARTSPACE_CARD_SEEN_EVENT,
surface,
- visibleIndex
+ visibleIndex,
)
}
}
@@ -409,7 +377,7 @@
interactedSubCardRank: Int = 0,
interactedSubCardCardinality: Int = 0,
instanceId: InstanceId? = null,
- isRec: Boolean = false
+ isRec: Boolean = false,
) {
_currentMedia.value.forEachIndexed { index, mediaCommonModel ->
when (mediaCommonModel) {
@@ -423,7 +391,7 @@
surface,
mediaCommonModel.mediaLoadedModel.isSsReactivated,
interactedSubCardRank,
- interactedSubCardCardinality
+ interactedSubCardCardinality,
)
}
return
@@ -437,7 +405,7 @@
surface,
index,
interactedSubCardRank,
- interactedSubCardCardinality
+ interactedSubCardCardinality,
)
}
return
@@ -459,14 +427,14 @@
SMARTSPACE_CARD_DISMISS_EVENT,
surface,
mediaCommonModel.mediaLoadedModel.isSsReactivated,
- isSwipeToDismiss = true
+ isSwipeToDismiss = true,
)
is MediaCommonModel.MediaRecommendations ->
logSmarspaceRecommendationCardUserEvent(
SMARTSPACE_CARD_DISMISS_EVENT,
surface,
index,
- isSwipeToDismiss = true
+ isSwipeToDismiss = true,
)
}
}
@@ -513,7 +481,7 @@
isReactivated: Boolean,
interactedSubCardRank: Int = 0,
interactedSubCardCardinality: Int = 0,
- isSwipeToDismiss: Boolean = false
+ isSwipeToDismiss: Boolean = false,
) {
_selectedUserEntries.value[instanceId]?.let {
smartspaceLogger.logSmartspaceCardUIEvent(
@@ -537,7 +505,7 @@
index: Int,
interactedSubCardRank: Int = 0,
interactedSubCardCardinality: Int = 0,
- isSwipeToDismiss: Boolean = false
+ isSwipeToDismiss: Boolean = false,
) {
smartspaceLogger.logSmartspaceCardUIEvent(
eventId,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
index 1f339dd..09aa85b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
@@ -68,8 +68,6 @@
.map { entries -> entries[instanceId]?.let { toMediaControlModel(it) } }
.distinctUntilChanged()
- val onAnyMediaConfigurationChange: Flow<Unit> = repository.onAnyMediaConfigurationChange
-
fun removeMediaControl(
token: MediaSession.Token?,
instanceId: InstanceId,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt
index c3a36b2..48ed391 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt
@@ -66,14 +66,12 @@
.distinctUntilChanged()
.stateIn(applicationScope, SharingStarted.WhileSubscribed(), false)
- val onAnyMediaConfigurationChange: Flow<Unit> = repository.onAnyMediaConfigurationChange
-
fun removeMediaRecommendations(
key: String,
dismissIntent: Intent?,
delayMs: Long,
eventId: Int,
- location: Int
+ location: Int,
) {
logSmartspaceCardUserEvent(eventId, location)
mediaDataProcessor.dismissSmartspaceRecommendation(key, delayMs)
@@ -101,7 +99,7 @@
eventId: Int,
location: Int,
interactedSubCardRank: Int,
- interactedSubCardCardinality: Int
+ interactedSubCardCardinality: Int,
) {
if (interactedSubCardRank == -1) {
logSmartspaceCardUserEvent(eventId, MediaSmartspaceLogger.getSurface(location))
@@ -111,7 +109,7 @@
MediaSmartspaceLogger.getSurface(location),
interactedSubCardRank = interactedSubCardRank,
interactedSubCardCardinality = interactedSubCardCardinality,
- isRec = true
+ isRec = true,
)
}
if (shouldActivityOpenInForeground(intent)) {
@@ -121,7 +119,7 @@
0 /* delay */,
expandable.activityTransitionController(
InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER
- )
+ ),
)
} else {
// Otherwise, open the activity in background directly.
@@ -133,7 +131,7 @@
repository.logSmartspaceCardUserEvent(
eventId,
MediaSmartspaceLogger.getSurface(location),
- isRec = true
+ isRec = true,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
index a6e1582..910d3a8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
@@ -34,6 +34,7 @@
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.settingslib.widget.AdaptiveIcon
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
@@ -64,7 +65,6 @@
import com.android.systemui.surfaceeffects.ripple.RippleShader
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.collectLatest
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.withContext
private const val TAG = "MediaControlViewBinder"
@@ -85,7 +85,7 @@
launch {
viewModel.player.collectLatest { player ->
player?.let {
- if (viewModel.isNewPlayer(it)) {
+ if (viewModel.setPlayer(it)) {
bindMediaCard(
viewHolder,
viewController,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 43c2011..f0f8a95 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -37,6 +37,7 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.DiffUtil
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.app.tracing.traceSection
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
@@ -115,7 +116,6 @@
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.withContext
private const val TAG = "MediaCarouselController"
@@ -752,7 +752,11 @@
}
}
- private fun onAdded(commonViewModel: MediaCommonViewModel, position: Int) {
+ private fun onAdded(
+ commonViewModel: MediaCommonViewModel,
+ position: Int,
+ configChanged: Boolean = false,
+ ) {
val viewController = mediaViewControllerFactory.get()
viewController.sizeChangedListener = this::updateCarouselDimensions
val lp =
@@ -763,12 +767,13 @@
when (commonViewModel) {
is MediaCommonViewModel.MediaControl -> {
val viewHolder = MediaViewHolder.create(LayoutInflater.from(context), mediaContent)
- if (SceneContainerFlag.isEnabled) {
- viewController.widthInSceneContainerPx = widthInSceneContainerPx
- viewController.heightInSceneContainerPx = heightInSceneContainerPx
- }
+ viewController.widthInSceneContainerPx = widthInSceneContainerPx
+ viewController.heightInSceneContainerPx = heightInSceneContainerPx
viewController.attachPlayer(viewHolder)
viewController.mediaViewHolder?.player?.layoutParams = lp
+ if (configChanged) {
+ commonViewModel.controlViewModel.onMediaConfigChanged()
+ }
MediaControlViewBinder.bind(
viewHolder,
commonViewModel.controlViewModel,
@@ -1271,23 +1276,14 @@
ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator))
if (recreateMedia) {
mediaContent.removeAllViews()
- commonViewModels.forEach { viewModel ->
+ commonViewModels.forEachIndexed { index, viewModel ->
when (viewModel) {
- is MediaCommonViewModel.MediaControl -> {
- controllerById[viewModel.instanceId.toString()]?.let {
- it.widthInSceneContainerPx = widthInSceneContainerPx
- it.heightInSceneContainerPx = heightInSceneContainerPx
- mediaContent.addView(it.mediaViewHolder?.player)
- }
- }
- is MediaCommonViewModel.MediaRecommendations -> {
- controllerById[viewModel.key]?.let {
- it.widthInSceneContainerPx = widthInSceneContainerPx
- it.heightInSceneContainerPx = heightInSceneContainerPx
- mediaContent.addView(it.recommendationViewHolder?.recommendations)
- }
- }
+ is MediaCommonViewModel.MediaControl ->
+ controllerById[viewModel.instanceId.toString()]?.onDestroy()
+ is MediaCommonViewModel.MediaRecommendations ->
+ controllerById[viewModel.key]?.onDestroy()
}
+ onAdded(viewModel, index, configChanged = true)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
index 4173d2a..4e97f20 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
@@ -41,10 +41,8 @@
import com.android.systemui.res.R
import java.util.concurrent.Executor
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
@@ -56,15 +54,9 @@
private val interactor: MediaControlInteractor,
private val logger: MediaUiEventLogger,
) {
-
- @OptIn(ExperimentalCoroutinesApi::class)
val player: Flow<MediaPlayerViewModel?> =
- interactor.onAnyMediaConfigurationChange
- .flatMapLatest {
- interactor.mediaControl.map { mediaControl ->
- mediaControl?.let { toViewModel(it) }
- }
- }
+ interactor.mediaControl
+ .map { mediaControl -> mediaControl?.let { toViewModel(it) } }
.distinctUntilChanged { old, new ->
(new == null && old == null) || new?.contentEquals(old) ?: false
}
@@ -74,14 +66,21 @@
private var isAnyButtonClicked = false
@MediaLocation private var location = MediaHierarchyManager.LOCATION_UNKNOWN
private var playerViewModel: MediaPlayerViewModel? = null
+ private var allowPlayerUpdate: Boolean = false
- fun isNewPlayer(viewModel: MediaPlayerViewModel): Boolean {
- val contentEquals = playerViewModel?.contentEquals(viewModel) ?: false
- return (!contentEquals).also { playerViewModel = viewModel }
+ fun setPlayer(viewModel: MediaPlayerViewModel): Boolean {
+ val tempViewModel = playerViewModel
+ playerViewModel = viewModel
+ return allowPlayerUpdate || !(tempViewModel?.contentEquals(viewModel) ?: false)
+ }
+
+ fun onMediaConfigChanged() {
+ allowPlayerUpdate = true
}
fun onMediaControlIsBound(artistName: CharSequence, titleName: CharSequence) {
interactor.logMediaControlIsBound(artistName, titleName)
+ allowPlayerUpdate = false
}
private fun onDismissMediaData(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt
index 6bc6b10a..88cfbaf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt
@@ -41,10 +41,8 @@
import com.android.systemui.res.R
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
@@ -59,12 +57,9 @@
private val logger: MediaUiEventLogger,
) {
- @OptIn(ExperimentalCoroutinesApi::class)
val mediaRecsCard: Flow<MediaRecsCardViewModel?> =
- interactor.onAnyMediaConfigurationChange
- .flatMapLatest {
- interactor.recommendations.map { recsCard -> toRecsViewModel(recsCard) }
- }
+ interactor.recommendations
+ .map { recsCard -> toRecsViewModel(recsCard) }
.distinctUntilChanged()
.flowOn(backgroundDispatcher)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
index 9c63456..0051bf5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
@@ -109,6 +109,11 @@
userActionInteractor.handleClick(expandable)
}
+ override fun handleSecondaryClick(expandable: Expandable?) = runBlocking {
+ val model = dataInteractor.getCurrentTileModel()
+ userActionInteractor.handleToggleClick(model)
+ }
+
override fun getLongClickIntent(): Intent = userActionInteractor.longClickIntent
@VisibleForTesting
@@ -125,6 +130,7 @@
secondaryLabel = tileState.secondaryLabel
contentDescription = tileState.contentDescription
expandedAccessibilityClassName = tileState.expandedAccessibilityClassName
+ handlesSecondaryClick = true
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 244f024..bed8021 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -149,7 +149,7 @@
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final TelephonyDisplayInfo DEFAULT_TELEPHONY_DISPLAY_INFO =
new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
- TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false);
+ TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false, false, false);
static final int MAX_WIFI_ENTRY_COUNT = 3;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
index eb8b23c..594394f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
@@ -18,13 +18,16 @@
import android.content.Intent
import android.provider.Settings
+import android.util.Log
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.flags.QSComposeFragment
import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.interactor.QSTileInput
import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
import javax.inject.Inject
@@ -35,16 +38,19 @@
private val qsTileIntentUserInputHandler: QSTileIntentUserInputHandler,
// TODO(b/353896370): The domain layer should not have to depend on the UI layer.
private val dialogDelegate: ModesDialogDelegate,
+ private val zenModeInteractor: ZenModeInteractor,
) : QSTileUserActionInteractor<ModesTileModel> {
val longClickIntent = Intent(Settings.ACTION_ZEN_MODE_SETTINGS)
override suspend fun handleInput(input: QSTileInput<ModesTileModel>) {
with(input) {
when (action) {
- is QSTileUserAction.Click,
- is QSTileUserAction.ToggleClick -> {
+ is QSTileUserAction.Click -> {
handleClick(action.expandable)
}
+ is QSTileUserAction.ToggleClick -> {
+ handleToggleClick(input.data)
+ }
is QSTileUserAction.LongClick -> {
qsTileIntentUserInputHandler.handle(action.expandable, longClickIntent)
}
@@ -56,4 +62,29 @@
// Show a dialog with the list of modes to configure.
dialogDelegate.showDialog(expandable)
}
+
+ fun handleToggleClick(modesTileModel: ModesTileModel) {
+ if (QSComposeFragment.isUnexpectedlyInLegacyMode()) {
+ return
+ }
+
+ // If no modes are on, turn on DND since it's the highest-priority mode. Otherwise, turn
+ // them all off.
+ // We want this toggle to work as a shortcut to DND in most cases, but it should still
+ // correctly toggle the tile state to "off" as the user would expect when more modes are on.
+ if (modesTileModel.activeModes.isEmpty()) {
+ val dnd = zenModeInteractor.dndMode.value
+ if (dnd == null) {
+ Log.wtf(TAG, "Triggered DND but it's null!?")
+ return
+ }
+ zenModeInteractor.activateMode(dnd)
+ } else {
+ zenModeInteractor.deactivateAllModes()
+ }
+ }
+
+ companion object {
+ const val TAG = "ModesTileUserActionInteractor"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
index bac048f..1507ef4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
@@ -45,7 +45,11 @@
secondaryLabel = getModesStatus(data, resources)
contentDescription = "$label. $secondaryLabel"
supportedActions =
- setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ setOf(
+ QSTileState.UserAction.CLICK,
+ QSTileState.UserAction.LONG_CLICK,
+ QSTileState.UserAction.TOGGLE_CLICK,
+ )
sideViewIcon = QSTileState.SideViewIcon.Chevron
expandedAccessibilityClass = Button::class
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
index c37b01f..a9c2784 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
@@ -49,7 +49,7 @@
) : ConnectivityState() {
@JvmField var telephonyDisplayInfo = TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
- TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false)
+ TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false, false, false)
@JvmField var serviceState: ServiceState? = null
@JvmField var signalStrength: SignalStrength? = null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
index 9839f9d..12ed647 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
@@ -40,12 +40,16 @@
import java.time.Duration
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
/**
* An interactor that performs business logic related to the status and configuration of Zen Mode
@@ -58,6 +62,7 @@
private val zenModeRepository: ZenModeRepository,
private val notificationSettingsRepository: NotificationSettingsRepository,
@Background private val bgDispatcher: CoroutineDispatcher,
+ @Background private val backgroundScope: CoroutineScope,
private val iconLoader: ZenIconLoader,
deviceProvisioningRepository: DeviceProvisioningRepository,
userSetupRepository: UserSetupRepository,
@@ -101,13 +106,16 @@
/**
* Returns the special "manual DND" mode.
*
- * This is only meant as a temporary solution for "legacy" UI pieces that handle DND
- * specifically; any new or migrated features should use modes more generally, through [modes]
- * or [activeModes].
+ * This should only be used when there is a strong reason to handle DND specifically (such as
+ * legacy UI pieces that haven't been updated to use modes more generally, or if the user
+ * explicitly wants a shortcut to DND). Please prefer using [modes] or [activeModes] in all
+ * other scenarios.
*/
- val dndMode: Flow<ZenMode?> by lazy {
+ val dndMode: StateFlow<ZenMode?> by lazy {
ModesUi.assertInNewMode()
- zenModeRepository.modes.map { modes -> modes.singleOrNull { it.isManualDnd } }
+ zenModeRepository.modes
+ .map { modes -> modes.singleOrNull { it.isManualDnd } }
+ .stateIn(scope = backgroundScope, started = SharingStarted.Eagerly, initialValue = null)
}
/** Flow returning the currently active mode(s), if any. */
@@ -201,6 +209,14 @@
zenModeRepository.deactivateMode(zenMode)
}
+ fun deactivateAllModes() {
+ for (mode in zenModeRepository.getModes()) {
+ if (mode.isActive) {
+ deactivateMode(mode)
+ }
+ }
+ }
+
private val zenDuration
get() = notificationSettingsRepository.zenDuration.value
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
index f98ad45..3bd2721 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
@@ -196,15 +196,17 @@
uiModel.availableButtons.fastForEachIndexed { index, ringerButton ->
ringerButton?.let {
val view = getChildAt(count - index - 1)
+ val isOpen = uiModel.drawerState is RingerDrawerState.Open
if (index == uiModel.currentButtonIndex) {
view.bindDrawerButton(
- uiModel.selectedButton,
+ if (isOpen) it else uiModel.selectedButton,
viewModel,
+ isOpen,
isSelected = true,
isAnimated = isAnimated,
)
} else {
- view.bindDrawerButton(it, viewModel, isAnimated)
+ view.bindDrawerButton(it, viewModel, isOpen, isAnimated = isAnimated)
}
}
}
@@ -214,12 +216,22 @@
private fun View.bindDrawerButton(
buttonViewModel: RingerButtonViewModel,
viewModel: VolumeDialogRingerDrawerViewModel,
+ isOpen: Boolean,
isSelected: Boolean = false,
isAnimated: Boolean = false,
) {
+ val ringerContentDesc = context.getString(buttonViewModel.contentDescriptionResId)
with(requireViewById<ImageButton>(R.id.volume_drawer_button)) {
setImageResource(buttonViewModel.imageResId)
- contentDescription = context.getString(buttonViewModel.contentDescriptionResId)
+ contentDescription =
+ if (isSelected && !isOpen) {
+ context.getString(
+ R.string.volume_ringer_drawer_closed_content_description,
+ ringerContentDesc,
+ )
+ } else {
+ ringerContentDesc
+ }
if (isSelected && !isAnimated) {
setBackgroundResource(R.drawable.volume_drawer_selection_bg)
setColorFilter(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index e565de5..02747d7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -17,7 +17,9 @@
package com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel
import android.content.Context
+import android.graphics.Color as GraphicsColor
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.Flags
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Color
import com.android.systemui.common.shared.model.Icon
@@ -77,7 +79,13 @@
ConnectedDeviceViewModel(
label = label,
labelColor =
- Color.Attribute(com.android.internal.R.attr.materialColorOnSurfaceVariant),
+ if (Flags.volumeRedesign()) {
+ Color.Attribute(com.android.internal.R.attr.materialColorOnSurface)
+ } else {
+ Color.Attribute(
+ com.android.internal.R.attr.materialColorOnSurfaceVariant
+ )
+ },
deviceName =
if (mediaOutputModel.isInAudioSharing) {
context.getString(R.string.audio_sharing_description)
@@ -96,11 +104,7 @@
},
)
}
- .stateIn(
- coroutineScope,
- SharingStarted.Eagerly,
- null,
- )
+ .stateIn(coroutineScope, SharingStarted.Eagerly, null)
val deviceIconViewModel: StateFlow<DeviceIconViewModel?> =
mediaOutputComponentInteractor.mediaOutputModel
@@ -121,7 +125,15 @@
icon = icon,
iconColor =
if (mediaOutputModel.canOpenAudioSwitcher) {
- Color.Attribute(com.android.internal.R.attr.materialColorSurface)
+ if (Flags.volumeRedesign()) {
+ Color.Attribute(
+ com.android.internal.R.attr.materialColorOnPrimary
+ )
+ } else {
+ Color.Attribute(
+ com.android.internal.R.attr.materialColorSurface
+ )
+ }
} else {
Color.Attribute(
com.android.internal.R.attr.materialColorSurfaceContainerHighest
@@ -129,7 +141,15 @@
},
backgroundColor =
if (mediaOutputModel.canOpenAudioSwitcher) {
- Color.Attribute(com.android.internal.R.attr.materialColorSecondary)
+ if (Flags.volumeRedesign()) {
+ Color.Attribute(
+ com.android.internal.R.attr.materialColorPrimary
+ )
+ } else {
+ Color.Attribute(
+ com.android.internal.R.attr.materialColorSecondary
+ )
+ }
} else {
Color.Attribute(com.android.internal.R.attr.materialColorOutline)
},
@@ -139,38 +159,29 @@
icon = icon,
iconColor =
if (mediaOutputModel.canOpenAudioSwitcher) {
- Color.Attribute(
- com.android.internal.R.attr.materialColorOnSurfaceVariant
- )
+ if (Flags.volumeRedesign()) {
+ Color.Attribute(
+ com.android.internal.R.attr.materialColorPrimary
+ )
+ } else {
+ Color.Attribute(
+ com.android.internal.R.attr.materialColorOnSurfaceVariant
+ )
+ }
} else {
Color.Attribute(com.android.internal.R.attr.materialColorOutline)
},
- backgroundColor =
- if (mediaOutputModel.canOpenAudioSwitcher) {
- Color.Attribute(com.android.internal.R.attr.materialColorSurface)
- } else {
- Color.Attribute(
- com.android.internal.R.attr.materialColorSurfaceContainerHighest
- )
- },
+ backgroundColor = Color.Loaded(GraphicsColor.TRANSPARENT),
)
}
}
- .stateIn(
- coroutineScope,
- SharingStarted.Eagerly,
- null,
- )
+ .stateIn(coroutineScope, SharingStarted.Eagerly, null)
val enabled: StateFlow<Boolean> =
mediaOutputComponentInteractor.mediaOutputModel
.filterData()
.map { it.canOpenAudioSwitcher }
- .stateIn(
- coroutineScope,
- SharingStarted.Eagerly,
- true,
- )
+ .stateIn(coroutineScope, SharingStarted.Eagerly, true)
fun onBarClick(expandable: Expandable?) {
uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_MEDIA_OUTPUT_CLICKED)
@@ -178,7 +189,7 @@
mediaOutputComponentInteractor.mediaOutputModel.value
actionsInteractor.onBarClick(
(result as? Result.Data<MediaOutputComponentModel>)?.data,
- expandable
+ expandable,
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index ae94544..f347d48 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -90,6 +90,7 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.clearInvocations
@RunWith(AndroidJUnit4::class)
@SmallTest
@@ -536,6 +537,7 @@
.onZenDataChanged(
eq(ZenData(ZenMode.IMPORTANT_INTERRUPTIONS, R.string::dnd_is_on.name))
)
+ clearInvocations(events)
zenModeRepository.deactivateMode(dndModeId)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 194f456..38acd23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -137,6 +137,7 @@
import com.android.systemui.util.settings.SystemSettings;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.wallpapers.data.repository.FakeWallpaperRepository;
+import com.android.window.flags.Flags;
import com.android.wm.shell.keyguard.KeyguardTransitions;
import kotlinx.coroutines.CoroutineDispatcher;
@@ -157,6 +158,10 @@
@TestableLooper.RunWithLooper
@SmallTest
public class KeyguardViewMediatorTest extends SysuiTestCase {
+
+ private static final boolean ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS =
+ Flags.ensureKeyguardDoesTransitionStarting();
+
private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
private KeyguardViewMediator mViewMediator;
@@ -1164,6 +1169,29 @@
*/
private void assertATMSLockScreenShowing(boolean showing)
throws RemoteException {
+
+ if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
+ // ATMS is called via bgExecutor, so make sure to run all of those calls first.
+ processAllMessagesAndBgExecutorMessages();
+
+ final InOrder orderedSetLockScreenShownCalls = inOrder(mKeyguardTransitions);
+ final ArgumentCaptor<Boolean> showingCaptor = ArgumentCaptor.forClass(Boolean.class);
+ orderedSetLockScreenShownCalls
+ .verify(mKeyguardTransitions, atLeastOnce())
+ .startKeyguardTransition(showingCaptor.capture(), anyBoolean());
+
+ // The captor will have the most recent startKeyguardTransition call's value.
+ assertEquals(showing, showingCaptor.getValue());
+
+ // We're now just after the last startKeyguardTransition call. If we expect the
+ // lockscreen to be showing, ensure that we didn't subsequently ask for it to go away.
+ if (showing) {
+ orderedSetLockScreenShownCalls.verify(mKeyguardTransitions, never())
+ .startKeyguardTransition(eq(false), anyBoolean());
+ }
+ return;
+ }
+
// ATMS is called via bgExecutor, so make sure to run all of those calls first.
processAllMessagesAndBgExecutorMessages();
@@ -1192,6 +1220,20 @@
// ATMS is called via bgExecutor, so make sure to run all of those calls first.
processAllMessagesAndBgExecutorMessages();
+ if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
+ final InOrder orderedGoingAwayCalls = inOrder(mKeyguardTransitions);
+ orderedGoingAwayCalls.verify(mKeyguardTransitions, atLeastOnce())
+ .startKeyguardTransition(eq(false) /* keyguardShowing */,
+ eq(false) /* aodShowing */);
+
+ // Advance the inOrder to just past the last goingAway call. Let's make sure we didn't
+ // re-show the lockscreen, which would cancel going away.
+ orderedGoingAwayCalls.verify(mKeyguardTransitions, never())
+ .startKeyguardTransition(eq(true) /* keyguardShowing */,
+ anyBoolean() /* aodShowing */);
+ return;
+ }
+
final InOrder orderedGoingAwayCalls = inOrder(mActivityTaskManagerService);
orderedGoingAwayCalls.verify(mActivityTaskManagerService, atLeastOnce())
.keyguardGoingAway(anyInt());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 7634490..728f418 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -655,14 +655,14 @@
// CDMA roaming is off, GSM roaming is off
whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
cb.onDisplayInfoChanged(
- TelephonyDisplayInfo(NETWORK_TYPE_LTE, NETWORK_TYPE_UNKNOWN, false)
+ TelephonyDisplayInfo(NETWORK_TYPE_LTE, NETWORK_TYPE_UNKNOWN, false, false, false)
)
assertThat(latest).isFalse()
// CDMA roaming is off, GSM roaming is on
cb.onDisplayInfoChanged(
- TelephonyDisplayInfo(NETWORK_TYPE_LTE, NETWORK_TYPE_UNKNOWN, true)
+ TelephonyDisplayInfo(NETWORK_TYPE_LTE, NETWORK_TYPE_UNKNOWN, true, false, false)
)
assertThat(latest).isTrue()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FingerprintManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FingerprintManagerKosmos.kt
new file mode 100644
index 0000000..470a8e4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FingerprintManagerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.hardware.fingerprint.FingerprintManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.fingerprintManager by Kosmos.Fixture { mock<FingerprintManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt
index ae592b9..646c190 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt
@@ -17,20 +17,19 @@
package com.android.systemui.biometrics.domain.interactor
import android.content.applicationContext
-import android.hardware.fingerprint.FingerprintManager
import com.android.systemui.biometrics.authController
+import com.android.systemui.biometrics.fingerprintManager
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.user.domain.interactor.selectedUserInteractor
-import com.android.systemui.util.mockito.mock
val Kosmos.udfpsOverlayInteractor by Fixture {
UdfpsOverlayInteractor(
context = applicationContext,
authController = authController,
selectedUserInteractor = selectedUserInteractor,
- fingerprintManager = mock<FingerprintManager>(),
+ fingerprintManager = fingerprintManager,
scope = applicationCoroutineScope,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt
index 7a04aa2..7964c11 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt
@@ -19,7 +19,6 @@
import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.media.controls.util.mediaSmartspaceLogger
-import com.android.systemui.statusbar.policy.configurationController
import com.android.systemui.util.time.systemClock
val Kosmos.mediaFilterRepository by
@@ -27,7 +26,6 @@
MediaFilterRepository(
applicationContext = applicationContext,
systemClock = systemClock,
- configurationController = configurationController,
smartspaceLogger = mediaSmartspaceLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt
index 2ecfb45..3c37101 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt
@@ -18,6 +18,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import com.android.systemui.statusbar.policy.ui.dialog.modesDialogDelegate
import javax.inject.Provider
@@ -26,5 +27,6 @@
ModesTileUserActionInteractor(
qsTileIntentUserInputHandler,
Provider { modesDialogDelegate }.get(),
+ zenModeInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
index 68d08e2..bbccbb1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
@@ -20,6 +20,7 @@
import com.android.settingslib.notification.modes.zenIconLoader
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.backgroundScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.shared.notifications.data.repository.notificationSettingsRepository
import com.android.systemui.statusbar.policy.data.repository.deviceProvisioningRepository
@@ -32,6 +33,7 @@
zenModeRepository = zenModeRepository,
notificationSettingsRepository = notificationSettingsRepository,
bgDispatcher = testDispatcher,
+ backgroundScope = backgroundScope,
iconLoader = zenIconLoader,
deviceProvisioningRepository = deviceProvisioningRepository,
userSetupRepository = userSetupRepository,
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 66c8d0f..59043a83 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -182,21 +182,6 @@
visibility: [":__subpackages__"],
}
-// Separated out from ravenwood-junit-impl since it needs to compile
-// against `module_current`
-java_library {
- name: "ravenwood-junit-impl-flag",
- srcs: [
- "junit-flag-src/**/*.java",
- ],
- sdk_version: "module_current",
- libs: [
- "junit",
- "flag-junit",
- ],
- visibility: ["//visibility:public"],
-}
-
// Carefully compiles against only module_current to support tests that
// want to verify they're unbundled. The "impl" library above is what
// ships inside the Ravenwood environment to actually drive any API
@@ -651,7 +636,6 @@
"flag-junit",
"ravenwood-framework",
"ravenwood-junit-impl",
- "ravenwood-junit-impl-flag",
"mockito-ravenwood-prebuilt",
"inline-mockito-ravenwood-prebuilt",
diff --git a/ravenwood/junit-flag-src/android/platform/test/flag/junit/RavenwoodFlagsValueProvider.java b/ravenwood/junit-flag-src/android/platform/test/flag/junit/RavenwoodFlagsValueProvider.java
deleted file mode 100644
index 9d62774..0000000
--- a/ravenwood/junit-flag-src/android/platform/test/flag/junit/RavenwoodFlagsValueProvider.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.platform.test.flag.junit;
-
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.IFlagsValueProvider;
-
-/**
- * Offer to create {@link CheckFlagsRule} instances that are useful on the Ravenwood deviceless
- * testing environment.
- *
- * At the moment, default flag values are not available on Ravenwood, so the only options offered
- * here are "all-on" and "all-off" options. Tests that want to exercise specific flag states should
- * use {@link android.platform.test.flag.junit.SetFlagsRule}.
- */
-public class RavenwoodFlagsValueProvider {
- /**
- * Create a {@link CheckFlagsRule} instance where flags are in an "all-on" state.
- */
- public static CheckFlagsRule createAllOnCheckFlagsRule() {
- return new CheckFlagsRule(new IFlagsValueProvider() {
- @Override
- public boolean getBoolean(String flag) {
- return true;
- }
- });
- }
-
- /**
- * Create a {@link CheckFlagsRule} instance where flags are in an "all-off" state.
- */
- public static CheckFlagsRule createAllOffCheckFlagsRule() {
- return new CheckFlagsRule(new IFlagsValueProvider() {
- @Override
- public boolean getBoolean(String flag) {
- return false;
- }
- });
- }
-}
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index d84a892..ccc44a4 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -16,14 +16,9 @@
package com.android.server;
-import static android.service.quickaccesswallet.Flags.launchWalletOptionOnPowerDoubleTap;
-
-import static com.android.hardware.input.Flags.overridePowerKeyBehaviorInFocusedWindow;
import static com.android.internal.R.integer.config_defaultMinEmergencyGestureTapDurationMillis;
import android.app.ActivityManager;
-import android.app.ActivityOptions;
-import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -38,7 +33,6 @@
import android.hardware.SensorManager;
import android.hardware.TriggerEvent;
import android.hardware.TriggerEventListener;
-import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
@@ -47,12 +41,10 @@
import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
-import android.service.quickaccesswallet.QuickAccessWalletClient;
import android.util.MutableBoolean;
import android.util.Slog;
import android.view.KeyEvent;
-import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEvent;
@@ -78,8 +70,7 @@
* Time in milliseconds in which the power button must be pressed twice so it will be considered
* as a camera launch.
*/
- @VisibleForTesting
- static final long POWER_DOUBLE_TAP_MAX_TIME_MS = 300;
+ @VisibleForTesting static final long CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS = 300;
/**
@@ -109,23 +100,10 @@
@VisibleForTesting
static final int EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX = 5000;
- /** Indicates camera should be launched on power double tap. */
- @VisibleForTesting static final int LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER = 0;
-
- /** Indicates wallet should be launched on power double tap. */
- @VisibleForTesting static final int LAUNCH_WALLET_ON_DOUBLE_TAP_POWER = 1;
-
- /** Number of taps required to launch the double tap shortcut (either camera or wallet). */
- public static final int DOUBLE_POWER_TAP_COUNT_THRESHOLD = 2;
-
- /** Bundle to send with PendingIntent to grant background activity start privileges. */
- private static final Bundle GRANT_BACKGROUND_START_PRIVILEGES =
- ActivityOptions.makeBasic()
- .setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS)
- .toBundle();
-
- private final QuickAccessWalletClient mQuickAccessWalletClient;
+ /**
+ * Number of taps required to launch camera shortcut.
+ */
+ private static final int CAMERA_POWER_TAP_COUNT_THRESHOLD = 2;
/** The listener that receives the gesture event. */
private final GestureEventListener mGestureListener = new GestureEventListener();
@@ -180,9 +158,6 @@
*/
private boolean mCameraDoubleTapPowerEnabled;
- /** Whether wallet double tap power button gesture is currently enabled. */
- private boolean mWalletDoubleTapPowerEnabled;
-
/**
* Whether emergency gesture is currently enabled
*/
@@ -229,22 +204,14 @@
}
}
public GestureLauncherService(Context context) {
- this(
- context,
- new MetricsLogger(),
- QuickAccessWalletClient.create(context),
- new UiEventLoggerImpl());
+ this(context, new MetricsLogger(), new UiEventLoggerImpl());
}
@VisibleForTesting
- public GestureLauncherService(
- Context context,
- MetricsLogger metricsLogger,
- QuickAccessWalletClient quickAccessWalletClient,
+ GestureLauncherService(Context context, MetricsLogger metricsLogger,
UiEventLogger uiEventLogger) {
super(context);
mContext = context;
- mQuickAccessWalletClient = quickAccessWalletClient;
mMetricsLogger = metricsLogger;
mUiEventLogger = uiEventLogger;
}
@@ -270,9 +237,6 @@
"GestureLauncherService");
updateCameraRegistered();
updateCameraDoubleTapPowerEnabled();
- if (launchWalletOptionOnPowerDoubleTap()) {
- updateWalletDoubleTapPowerEnabled();
- }
updateEmergencyGestureEnabled();
updateEmergencyGesturePowerButtonCooldownPeriodMs();
@@ -328,14 +292,6 @@
}
@VisibleForTesting
- void updateWalletDoubleTapPowerEnabled() {
- boolean enabled = isWalletDoubleTapPowerSettingEnabled(mContext, mUserId);
- synchronized (this) {
- mWalletDoubleTapPowerEnabled = enabled;
- }
- }
-
- @VisibleForTesting
void updateEmergencyGestureEnabled() {
boolean enabled = isEmergencyGestureSettingEnabled(mContext, mUserId);
synchronized (this) {
@@ -462,33 +418,10 @@
Settings.Secure.CAMERA_GESTURE_DISABLED, 0, userId) == 0);
}
- /** Checks if camera should be launched on double press of the power button. */
public static boolean isCameraDoubleTapPowerSettingEnabled(Context context, int userId) {
- boolean res;
-
- if (launchWalletOptionOnPowerDoubleTap()) {
- res = isDoubleTapPowerGestureSettingEnabled(context, userId)
- && getDoubleTapPowerGestureAction(context, userId)
- == LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER;
- } else {
- // These are legacy settings that will be deprecated once the option to launch both
- // wallet and camera has been created.
- res = isCameraDoubleTapPowerEnabled(context.getResources())
- && (Settings.Secure.getIntForUser(context.getContentResolver(),
- Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0, userId) == 0);
- }
- return res;
- }
-
- /** Checks if wallet should be launched on double tap of the power button. */
- public static boolean isWalletDoubleTapPowerSettingEnabled(Context context, int userId) {
- if (!launchWalletOptionOnPowerDoubleTap()) {
- return false;
- }
-
- return isDoubleTapPowerGestureSettingEnabled(context, userId)
- && getDoubleTapPowerGestureAction(context, userId)
- == LAUNCH_WALLET_ON_DOUBLE_TAP_POWER;
+ return isCameraDoubleTapPowerEnabled(context.getResources())
+ && (Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0, userId) == 0);
}
public static boolean isCameraLiftTriggerSettingEnabled(Context context, int userId) {
@@ -508,28 +441,6 @@
isDefaultEmergencyGestureEnabled(context.getResources()) ? 1 : 0, userId) != 0;
}
- private static int getDoubleTapPowerGestureAction(Context context, int userId) {
- return Settings.Secure.getIntForUser(
- context.getContentResolver(),
- Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE,
- LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER,
- userId);
- }
-
- /** Whether the shortcut to launch app on power double press is enabled. */
- private static boolean isDoubleTapPowerGestureSettingEnabled(Context context, int userId) {
- return Settings.Secure.getIntForUser(
- context.getContentResolver(),
- Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED,
- isDoubleTapConfigEnabled(context.getResources()) ? 1 : 0,
- userId)
- == 1;
- }
-
- private static boolean isDoubleTapConfigEnabled(Resources resources) {
- return resources.getBoolean(R.bool.config_doubleTapPowerGestureEnabled);
- }
-
/**
* Gets power button cooldown period in milliseconds after emergency gesture is triggered. The
* value is capped at a maximum
@@ -583,56 +494,10 @@
* Whether GestureLauncherService should be enabled according to system properties.
*/
public static boolean isGestureLauncherEnabled(Resources resources) {
- boolean res =
- isCameraLaunchEnabled(resources)
- || isCameraLiftTriggerEnabled(resources)
- || isEmergencyGestureEnabled(resources);
- if (launchWalletOptionOnPowerDoubleTap()) {
- res |= isDoubleTapConfigEnabled(resources);
- } else {
- res |= isCameraDoubleTapPowerEnabled(resources);
- }
- return res;
- }
-
- /**
- * Processes a power key event in GestureLauncherService without performing an action. This
- * method is called on every KEYCODE_POWER ACTION_DOWN event and ensures that, even if
- * KEYCODE_POWER events are passed to and handled by the app, the GestureLauncherService still
- * keeps track of all running KEYCODE_POWER events for its gesture detection and relevant
- * actions.
- */
- public void processPowerKeyDown(KeyEvent event) {
- if (mEmergencyGestureEnabled && mEmergencyGesturePowerButtonCooldownPeriodMs >= 0
- && event.getEventTime() - mLastEmergencyGestureTriggered
- < mEmergencyGesturePowerButtonCooldownPeriodMs) {
- return;
- }
- if (event.isLongPress()) {
- return;
- }
-
- final long powerTapInterval;
-
- synchronized (this) {
- powerTapInterval = event.getEventTime() - mLastPowerDown;
- mLastPowerDown = event.getEventTime();
- if (powerTapInterval >= POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS) {
- // Tap too slow, reset consecutive tap counts.
- mFirstPowerDown = event.getEventTime();
- mPowerButtonConsecutiveTaps = 1;
- mPowerButtonSlowConsecutiveTaps = 1;
- } else if (powerTapInterval >= POWER_DOUBLE_TAP_MAX_TIME_MS) {
- // Tap too slow for shortcuts
- mFirstPowerDown = event.getEventTime();
- mPowerButtonConsecutiveTaps = 1;
- mPowerButtonSlowConsecutiveTaps++;
- } else if (powerTapInterval > 0) {
- // Fast consecutive tap
- mPowerButtonConsecutiveTaps++;
- mPowerButtonSlowConsecutiveTaps++;
- }
- }
+ return isCameraLaunchEnabled(resources)
+ || isCameraDoubleTapPowerEnabled(resources)
+ || isCameraLiftTriggerEnabled(resources)
+ || isEmergencyGestureEnabled(resources);
}
/**
@@ -642,8 +507,8 @@
* @param outLaunched true if some action is taken as part of the key intercept (eg, app launch)
* @return true if the key down event is intercepted
*/
- public boolean interceptPowerKeyDown(
- KeyEvent event, boolean interactive, MutableBoolean outLaunched) {
+ public boolean interceptPowerKeyDown(KeyEvent event, boolean interactive,
+ MutableBoolean outLaunched) {
if (mEmergencyGestureEnabled && mEmergencyGesturePowerButtonCooldownPeriodMs >= 0
&& event.getEventTime() - mLastEmergencyGestureTriggered
< mEmergencyGesturePowerButtonCooldownPeriodMs) {
@@ -665,7 +530,6 @@
return false;
}
boolean launchCamera = false;
- boolean launchWallet = false;
boolean launchEmergencyGesture = false;
boolean intercept = false;
long powerTapInterval;
@@ -677,12 +541,12 @@
mFirstPowerDown = event.getEventTime();
mPowerButtonConsecutiveTaps = 1;
mPowerButtonSlowConsecutiveTaps = 1;
- } else if (powerTapInterval >= POWER_DOUBLE_TAP_MAX_TIME_MS) {
+ } else if (powerTapInterval >= CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS) {
// Tap too slow for shortcuts
mFirstPowerDown = event.getEventTime();
mPowerButtonConsecutiveTaps = 1;
mPowerButtonSlowConsecutiveTaps++;
- } else if (!overridePowerKeyBehaviorInFocusedWindow() || powerTapInterval > 0) {
+ } else {
// Fast consecutive tap
mPowerButtonConsecutiveTaps++;
mPowerButtonSlowConsecutiveTaps++;
@@ -722,16 +586,10 @@
}
}
if (mCameraDoubleTapPowerEnabled
- && powerTapInterval < POWER_DOUBLE_TAP_MAX_TIME_MS
- && mPowerButtonConsecutiveTaps == DOUBLE_POWER_TAP_COUNT_THRESHOLD) {
+ && powerTapInterval < CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS
+ && mPowerButtonConsecutiveTaps == CAMERA_POWER_TAP_COUNT_THRESHOLD) {
launchCamera = true;
intercept = interactive;
- } else if (launchWalletOptionOnPowerDoubleTap()
- && mWalletDoubleTapPowerEnabled
- && powerTapInterval < POWER_DOUBLE_TAP_MAX_TIME_MS
- && mPowerButtonConsecutiveTaps == DOUBLE_POWER_TAP_COUNT_THRESHOLD) {
- launchWallet = true;
- intercept = interactive;
}
}
if (mPowerButtonConsecutiveTaps > 1 || mPowerButtonSlowConsecutiveTaps > 1) {
@@ -750,10 +608,6 @@
(int) powerTapInterval);
mUiEventLogger.log(GestureLauncherEvent.GESTURE_CAMERA_DOUBLE_TAP_POWER);
}
- } else if (launchWallet) {
- Slog.i(TAG, "Power button double tap gesture detected, launching wallet. Interval="
- + powerTapInterval + "ms");
- launchWallet = sendGestureTargetActivityPendingIntent();
} else if (launchEmergencyGesture) {
Slog.i(TAG, "Emergency gesture detected, launching.");
launchEmergencyGesture = handleEmergencyGesture();
@@ -769,74 +623,11 @@
mPowerButtonSlowConsecutiveTaps);
mMetricsLogger.histogram("power_double_tap_interval", (int) powerTapInterval);
- outLaunched.value = launchCamera || launchEmergencyGesture || launchWallet;
+ outLaunched.value = launchCamera || launchEmergencyGesture;
// Intercept power key event if the press is part of a gesture (camera, eGesture) and the
// user has completed setup.
return intercept && isUserSetupComplete();
}
-
- /**
- * Fetches and sends gestureTargetActivityPendingIntent from QuickAccessWallet, which is a
- * specific activity that QuickAccessWalletService has defined to be launch on detection of the
- * power button gesture.
- */
- private boolean sendGestureTargetActivityPendingIntent() {
- boolean userSetupComplete = isUserSetupComplete();
- if (mQuickAccessWalletClient == null
- || !mQuickAccessWalletClient.isWalletServiceAvailable()) {
- Slog.w(TAG, "QuickAccessWalletService is not available, ignoring wallet gesture.");
- return false;
- }
-
- if (!userSetupComplete) {
- if (DBG) {
- Slog.d(TAG, "userSetupComplete = false, ignoring wallet gesture.");
- }
- return false;
- }
- if (DBG) {
- Slog.d(TAG, "userSetupComplete = true, performing wallet gesture.");
- }
-
- mQuickAccessWalletClient.getGestureTargetActivityPendingIntent(
- getContext().getMainExecutor(),
- gesturePendingIntent -> {
- if (gesturePendingIntent == null) {
- Slog.d(TAG, "getGestureTargetActivityPendingIntent is null.");
- sendFallbackPendingIntent();
- return;
- }
- sendPendingIntentWithBackgroundStartPrivileges(gesturePendingIntent);
- });
- return true;
- }
-
- /**
- * If gestureTargetActivityPendingIntent is null, this method is invoked to start the activity
- * that QuickAccessWalletService has defined to host the Wallet view, which is typically the
- * home screen of the Wallet application.
- */
- private void sendFallbackPendingIntent() {
- mQuickAccessWalletClient.getWalletPendingIntent(
- getContext().getMainExecutor(),
- walletPendingIntent -> {
- if (walletPendingIntent == null) {
- Slog.w(TAG, "getWalletPendingIntent returns null. Not launching "
- + "anything for wallet.");
- return;
- }
- sendPendingIntentWithBackgroundStartPrivileges(walletPendingIntent);
- });
- }
-
- private void sendPendingIntentWithBackgroundStartPrivileges(PendingIntent pendingIntent) {
- try {
- pendingIntent.send(GRANT_BACKGROUND_START_PRIVILEGES);
- } catch (PendingIntent.CanceledException e) {
- Slog.e(TAG, "PendingIntent was canceled", e);
- }
- }
-
/**
* @return true if camera was launched, false otherwise.
*/
@@ -909,39 +700,31 @@
Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
}
- private final BroadcastReceiver mUserReceiver =
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
- mUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
- mContext.getContentResolver().unregisterContentObserver(mSettingObserver);
- registerContentObservers();
- updateCameraRegistered();
- updateCameraDoubleTapPowerEnabled();
- if (launchWalletOptionOnPowerDoubleTap()) {
- updateWalletDoubleTapPowerEnabled();
- }
- updateEmergencyGestureEnabled();
- updateEmergencyGesturePowerButtonCooldownPeriodMs();
- }
- }
- };
+ private final BroadcastReceiver mUserReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
+ mUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+ mContext.getContentResolver().unregisterContentObserver(mSettingObserver);
+ registerContentObservers();
+ updateCameraRegistered();
+ updateCameraDoubleTapPowerEnabled();
+ updateEmergencyGestureEnabled();
+ updateEmergencyGesturePowerButtonCooldownPeriodMs();
+ }
+ }
+ };
- private final ContentObserver mSettingObserver =
- new ContentObserver(new Handler()) {
- public void onChange(boolean selfChange, android.net.Uri uri, int userId) {
- if (userId == mUserId) {
- updateCameraRegistered();
- updateCameraDoubleTapPowerEnabled();
- if (launchWalletOptionOnPowerDoubleTap()) {
- updateWalletDoubleTapPowerEnabled();
- }
- updateEmergencyGestureEnabled();
- updateEmergencyGesturePowerButtonCooldownPeriodMs();
- }
- }
- };
+ private final ContentObserver mSettingObserver = new ContentObserver(new Handler()) {
+ public void onChange(boolean selfChange, android.net.Uri uri, int userId) {
+ if (userId == mUserId) {
+ updateCameraRegistered();
+ updateCameraDoubleTapPowerEnabled();
+ updateEmergencyGestureEnabled();
+ updateEmergencyGesturePowerButtonCooldownPeriodMs();
+ }
+ }
+ };
private final class GestureEventListener implements SensorEventListener {
@Override
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index e57b009..eba9a25 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2166,7 +2166,11 @@
overrideNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE;
}
boolean isRoaming = telephonyDisplayInfo.isRoaming();
- return new TelephonyDisplayInfo(networkType, overrideNetworkType, isRoaming);
+ boolean isNtn = telephonyDisplayInfo.isNtn();
+ boolean isSatelliteConstrainedData =
+ telephonyDisplayInfo.isSatelliteConstrainedData();
+ return new TelephonyDisplayInfo(networkType, overrideNetworkType, isRoaming,
+ isNtn, isSatelliteConstrainedData);
}
public void notifyCallForwardingChanged(boolean cfi) {
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index d0153d8..1a8565a 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -146,6 +146,7 @@
"aaos_window_triage",
"aaos_audio_triage",
"aaos_power_triage",
+ "aaos_storage_triage",
"aaos_sdv",
"accessibility",
"android_core_networking",
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 440a271..860be20 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -32,6 +32,7 @@
import android.provider.DeviceConfigInterface;
import android.util.IndentingPrintWriter;
import android.util.Spline;
+import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.display.BrightnessSynchronizer;
@@ -58,6 +59,7 @@
private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;
private final Handler mHandler;
private final LightSensorController mLightSensorController;
+ private int mDisplayState = Display.STATE_OFF;
private final ClamperChangeListener mClamperChangeListenerExternal;
private final Executor mExecutor;
@@ -149,16 +151,13 @@
public DisplayBrightnessState clamp(DisplayBrightnessState displayBrightnessState,
DisplayManagerInternal.DisplayPowerRequest request,
float brightnessValue, boolean slowChange, int displayState) {
+ mDisplayState = displayState;
DisplayBrightnessState.Builder builder = DisplayBrightnessState.Builder.from(
displayBrightnessState);
builder.setIsSlowChange(slowChange);
builder.setBrightness(brightnessValue);
- if (displayState != STATE_ON) {
- mLightSensorController.stop();
- } else {
- adjustLightSensorSubscription();
- }
+ adjustLightSensorSubscription();
for (int i = 0; i < mModifiers.size(); i++) {
mModifiers.get(i).apply(request, builder);
@@ -230,7 +229,8 @@
}
private void adjustLightSensorSubscription() {
- if (mModifiers.stream().anyMatch(BrightnessStateModifier::shouldListenToLightSensor)) {
+ if (mDisplayState == STATE_ON && mModifiers.stream()
+ .anyMatch(BrightnessStateModifier::shouldListenToLightSensor)) {
mLightSensorController.restart();
} else {
mLightSensorController.stop();
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 265e453..bc44fed 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.graphics.PointF;
+import android.hardware.display.DisplayTopology;
import android.hardware.display.DisplayViewport;
import android.hardware.input.KeyGestureEvent;
import android.os.IBinder;
@@ -47,6 +48,12 @@
public abstract void setDisplayViewports(List<DisplayViewport> viewports);
/**
+ * Called by {@link com.android.server.display.DisplayManagerService} to inform InputManager
+ * about changes in the displays topology.
+ */
+ public abstract void setDisplayTopology(DisplayTopology topology);
+
+ /**
* Called by the power manager to tell the input manager whether it should start
* watching for wake events on given displays.
*
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 767a723..aee5e7f 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -51,6 +51,7 @@
import android.hardware.SensorPrivacyManager.Sensors;
import android.hardware.SensorPrivacyManagerInternal;
import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayTopology;
import android.hardware.display.DisplayViewport;
import android.hardware.input.AidlInputGestureData;
import android.hardware.input.HostUsiVersion;
@@ -660,6 +661,10 @@
mNative.setPointerDisplayId(mWindowManagerCallbacks.getPointerDisplayId());
}
+ private void setDisplayTopologyInternal(DisplayTopology topology) {
+ mNative.setDisplayTopology(topology.getGraph());
+ }
+
/**
* Gets the current state of a key or button by key code.
* @param deviceId The input device id, or -1 to consult all devices.
@@ -3461,6 +3466,11 @@
}
@Override
+ public void setDisplayTopology(DisplayTopology topology) {
+ setDisplayTopologyInternal(topology);
+ }
+
+ @Override
public void setDisplayInteractivities(SparseBooleanArray displayInteractivities) {
boolean globallyInteractive = false;
ArraySet<Integer> nonInteractiveDisplays = new ArraySet<>();
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 935f0ff..c72f7c0 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.display.DisplayTopologyGraph;
import android.hardware.display.DisplayViewport;
import android.hardware.input.InputSensorInfo;
import android.hardware.lights.Light;
@@ -42,6 +43,8 @@
void setDisplayViewports(DisplayViewport[] viewports);
+ void setDisplayTopology(DisplayTopologyGraph topologyGraph);
+
int getScanCodeState(int deviceId, int sourceMask, int scanCode);
int getKeyCodeState(int deviceId, int sourceMask, int keyCode);
@@ -323,6 +326,9 @@
public native void setDisplayViewports(DisplayViewport[] viewports);
@Override
+ public native void setDisplayTopology(DisplayTopologyGraph topologyGraph);
+
+ @Override
public native int getScanCodeState(int deviceId, int sourceMask, int scanCode);
@Override
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
index aa215ce..7155cd0 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
@@ -20,7 +20,6 @@
import android.hardware.contexthub.EndpointInfo;
import android.hardware.contexthub.HubEndpointInfo;
import android.hardware.contexthub.HubMessage;
-import android.hardware.contexthub.HubServiceInfo;
import android.hardware.contexthub.IContextHubEndpoint;
import android.hardware.contexthub.IContextHubEndpointCallback;
import android.hardware.location.IContextHubTransactionCallback;
@@ -81,7 +80,7 @@
}
@Override
- public int openSession(HubEndpointInfo destination, HubServiceInfo serviceInfo)
+ public int openSession(HubEndpointInfo destination, String serviceDescriptor)
throws RemoteException {
ContextHubServiceUtil.checkPermissions(mContext);
if (!mIsRegistered.get()) throw new IllegalStateException("Endpoint is not registered");
@@ -89,10 +88,7 @@
EndpointInfo halEndpointInfo = ContextHubServiceUtil.convertHalEndpointInfo(destination);
try {
mContextHubProxy.openEndpointSession(
- sessionId,
- halEndpointInfo.id,
- mHalEndpointInfo.id,
- serviceInfo == null ? null : serviceInfo.getServiceDescriptor());
+ sessionId, halEndpointInfo.id, mHalEndpointInfo.id, serviceDescriptor);
} catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
Log.e(TAG, "Exception while calling HAL openEndpointSession", e);
mEndpointManager.returnSessionId(sessionId);
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index ab68ed3..abc067d 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -2347,13 +2347,24 @@
if (!Flags.enableRouteVisibilityControlApi()) {
return true;
}
- for (String permission : route.getRequiredPermissions()) {
- if (mContext.checkPermission(permission, mPid, mUid)
- != PackageManager.PERMISSION_GRANTED) {
- return false;
+ List<Set<String>> permissionSets = route.getRequiredPermissions();
+ if (permissionSets.isEmpty()) {
+ return true;
+ }
+ for (Set<String> permissionSet : permissionSets) {
+ boolean hasAllInSet = true;
+ for (String permission : permissionSet) {
+ if (mContext.checkPermission(permission, mPid, mUid)
+ != PackageManager.PERMISSION_GRANTED) {
+ hasAllInSet = false;
+ break;
+ }
+ }
+ if (hasAllInSet) {
+ return true;
}
}
- return true;
+ return false;
}
}
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index 4479e9d..849751b 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -30,6 +30,7 @@
import android.media.quality.PictureProfile;
import android.media.quality.PictureProfileHandle;
import android.media.quality.SoundProfile;
+import android.media.quality.SoundProfileHandle;
import android.os.PersistableBundle;
import android.util.Log;
@@ -45,8 +46,8 @@
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
-import java.util.stream.Collectors;
import java.util.UUID;
+import java.util.stream.Collectors;
/**
* This service manage picture profile and sound profile for TV setting. Also communicates with the
@@ -173,8 +174,13 @@
}
@Override
- public PictureProfileHandle getPictureProfileHandle(String id, int userId) {
- return null;
+ public List<PictureProfileHandle> getPictureProfileHandle(String[] id, int userId) {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public List<SoundProfileHandle> getSoundProfileHandle(String[] id, int userId) {
+ return new ArrayList<>();
}
@Override
@@ -352,7 +358,8 @@
getName(cursor),
getInputId(cursor),
getPackageName(cursor),
- jsonToBundle(getSettingsString(cursor))
+ jsonToBundle(getSettingsString(cursor)),
+ PictureProfileHandle.NONE
);
}
@@ -363,7 +370,8 @@
getName(cursor),
getInputId(cursor),
getPackageName(cursor),
- jsonToBundle(getSettingsString(cursor))
+ jsonToBundle(getSettingsString(cursor)),
+ SoundProfileHandle.NONE
);
}
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index f79d9ef..65a38ae 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -123,13 +123,6 @@
}
flag {
- name: "use_ipcdatacache_channels"
- namespace: "systemui"
- description: "Adds an IPCDataCache for notification channel/group lookups"
- bug: "331677193"
-}
-
-flag {
name: "use_ssm_user_switch_signal"
namespace: "systemui"
description: "This flag controls which signal is used to handle a user switch system event"
diff --git a/services/core/java/com/android/server/pm/InstallDependencyHelper.java b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
index 673a102..c0ddebe 100644
--- a/services/core/java/com/android/server/pm/InstallDependencyHelper.java
+++ b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
@@ -129,14 +129,14 @@
}
}
- void notifySessionComplete(int sessionId, boolean success) {
+ void notifySessionComplete(int sessionId) {
if (DEBUG) {
- Slog.d(TAG, "Session complete for " + sessionId + " result: " + success);
+ Slog.d(TAG, "Session complete for " + sessionId);
}
synchronized (mTrackers) {
List<DependencyInstallTracker> completedTrackers = new ArrayList<>();
for (DependencyInstallTracker tracker: mTrackers) {
- if (!tracker.onSessionComplete(sessionId, success)) {
+ if (!tracker.onSessionComplete(sessionId)) {
completedTrackers.add(tracker);
}
}
@@ -354,7 +354,7 @@
// Don't wait for sessions that finished already
if (sessionInfo == null) {
Binder.withCleanCallingIdentity(() -> {
- notifySessionComplete(sessionId, /*success=*/ true);
+ notifySessionComplete(sessionId);
});
}
}
@@ -459,19 +459,13 @@
*
* Returns true if we still need to continue tracking.
*/
- public boolean onSessionComplete(int sessionId, boolean success) {
+ public boolean onSessionComplete(int sessionId) {
synchronized (this) {
if (!mPendingSessionIds.contains(sessionId)) {
// This had no impact on tracker, so continue tracking
return true;
}
- if (!success) {
- // If one of the dependency fails, the orig session would fail too.
- onError(mCallback, "Failed to install all dependencies");
- return false; // No point in tracking anymore
- }
-
mPendingSessionIds.remove(sessionId);
if (mPendingSessionIds.isEmpty()) {
mCallback.onResult(null);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 516b002..e1fcc66 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -2330,8 +2330,7 @@
}
if (Flags.sdkDependencyInstaller()) {
- mInstallDependencyHelper.notifySessionComplete(
- session.sessionId, success);
+ mInstallDependencyHelper.notifySessionComplete(session.sessionId);
}
final File appIconFile = buildAppIconFile(session.sessionId);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index e39906b..7c4d425 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -88,12 +88,10 @@
import static com.android.hardware.input.Flags.inputManagerLifecycleSupport;
import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
import static com.android.hardware.input.Flags.modifierShortcutDump;
-import static com.android.hardware.input.Flags.overridePowerKeyBehaviorInFocusedWindow;
import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
-import static com.android.server.GestureLauncherService.DOUBLE_POWER_TAP_COUNT_THRESHOLD;
import static com.android.server.flags.Flags.modifierShortcutManagerMultiuser;
import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED;
@@ -434,16 +432,6 @@
"android.intent.action.VOICE_ASSIST_RETAIL";
/**
- * Maximum amount of time in milliseconds between consecutive power onKeyDown events to be
- * considered a multi-press, only used for the power button.
- * Note: To maintain backwards compatibility for the power button, we are measuring the times
- * between consecutive down events instead of the first tap's up event and the second tap's
- * down event.
- */
- @VisibleForTesting public static final int POWER_MULTI_PRESS_TIMEOUT_MILLIS =
- ViewConfiguration.getMultiPressTimeout();
-
- /**
* Lock protecting internal state. Must not call out into window
* manager with lock held. (This lock will be acquired in places
* where the window manager is calling in with its own lock held.)
@@ -504,32 +492,6 @@
private WindowWakeUpPolicy mWindowWakeUpPolicy;
- /**
- * The three variables below are used for custom power key gesture detection in
- * PhoneWindowManager. They are used to detect when the power button has been double pressed
- * and, when it does happen, makes the behavior overrideable by the app.
- *
- * We cannot use the {@link PowerKeyRule} for this because multi-press power gesture detection
- * and behaviors are handled by {@link com.android.server.GestureLauncherService}, and the
- * {@link PowerKeyRule} only handles single and long-presses of the power button. As a result,
- * overriding the double tap behavior requires custom gesture detection here that mimics the
- * logic in {@link com.android.server.GestureLauncherService}.
- *
- * Long-term, it would be beneficial to move all power gesture detection to
- * {@link PowerKeyRule} so that this custom logic isn't required.
- */
- // Time of last power down event.
- private long mLastPowerDown;
-
- // Number of power button events consecutively triggered (within a specific timeout threshold).
- private int mPowerButtonConsecutiveTaps = 0;
-
- // Whether a double tap of the power button has been detected.
- volatile boolean mDoubleTapPowerDetected;
-
- // Runnable that is queued on a delay when the first power keyDown event is sent to the app.
- private Runnable mPowerKeyDelayedRunnable = null;
-
boolean mSafeMode;
// Whether to allow dock apps with METADATA_DOCK_HOME to temporarily take over the Home key.
@@ -1135,11 +1097,6 @@
mPowerKeyHandled = mPowerKeyHandled || hungUp
|| handledByPowerManager || isKeyGestureTriggered
|| mKeyCombinationManager.isPowerKeyIntercepted();
-
- if (overridePowerKeyBehaviorInFocusedWindow()) {
- mPowerKeyHandled |= mDoubleTapPowerDetected;
- }
-
if (!mPowerKeyHandled) {
if (!interactive) {
wakeUpFromWakeKey(event);
@@ -2712,19 +2669,7 @@
if (mShouldEarlyShortPressOnPower) {
return;
}
- // TODO(b/380433365): Remove deferring single power press action when refactoring.
- if (overridePowerKeyBehaviorInFocusedWindow()) {
- mDeferredKeyActionExecutor.cancelQueuedAction(KEYCODE_POWER);
- mDeferredKeyActionExecutor.queueKeyAction(
- KEYCODE_POWER,
- downTime,
- () -> {
- powerPress(downTime, 1 /*count*/, displayId);
- });
- } else {
- powerPress(downTime, 1 /*count*/, displayId);
- }
-
+ powerPress(downTime, 1 /*count*/, displayId);
}
@Override
@@ -2755,17 +2700,7 @@
@Override
void onMultiPress(long downTime, int count, int displayId) {
- if (overridePowerKeyBehaviorInFocusedWindow()) {
- mDeferredKeyActionExecutor.cancelQueuedAction(KEYCODE_POWER);
- mDeferredKeyActionExecutor.queueKeyAction(
- KEYCODE_POWER,
- downTime,
- () -> {
- powerPress(downTime, count, displayId);
- });
- } else {
- powerPress(downTime, count, displayId);
- }
+ powerPress(downTime, count, displayId);
}
@Override
@@ -3542,12 +3477,6 @@
}
}
- if (overridePowerKeyBehaviorInFocusedWindow() && event.getKeyCode() == KEYCODE_POWER
- && event.getAction() == KeyEvent.ACTION_UP
- && mDoubleTapPowerDetected) {
- mDoubleTapPowerDetected = false;
- }
-
return needToConsumeKey ? keyConsumed : keyNotConsumed;
}
@@ -4063,8 +3992,6 @@
sendSystemKeyToStatusBarAsync(event);
return true;
}
- case KeyEvent.KEYCODE_POWER:
- return interceptPowerKeyBeforeDispatching(focusedToken, event);
case KeyEvent.KEYCODE_SCREENSHOT:
if (firstDown) {
interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
@@ -4120,8 +4047,6 @@
sendSystemKeyToStatusBarAsync(event);
return true;
}
- case KeyEvent.KEYCODE_POWER:
- return interceptPowerKeyBeforeDispatching(focusedToken, event);
}
if (isValidGlobalKey(keyCode)
&& mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
@@ -4132,90 +4057,6 @@
return (metaState & KeyEvent.META_META_ON) != 0;
}
- /**
- * Called by interceptKeyBeforeDispatching to handle interception logic for KEYCODE_POWER
- * KeyEvents.
- *
- * @return true if intercepting the key, false if sending to app.
- */
- private boolean interceptPowerKeyBeforeDispatching(IBinder focusedToken, KeyEvent event) {
- if (!overridePowerKeyBehaviorInFocusedWindow()) {
- //Flag disabled: intercept the power key and do not send to app.
- return true;
- }
- if (event.getKeyCode() != KEYCODE_POWER) {
- Log.wtf(TAG, "interceptPowerKeyBeforeDispatching received a non-power KeyEvent "
- + "with key code: " + event.getKeyCode());
- return false;
- }
-
- // Intercept keys (don't send to app) for 3x, 4x, 5x gestures)
- if (mPowerButtonConsecutiveTaps > DOUBLE_POWER_TAP_COUNT_THRESHOLD) {
- setDeferredKeyActionsExecutableAsync(KEYCODE_POWER, event.getDownTime());
- return true;
- }
-
- // UP key; just reuse the original decision.
- if (event.getAction() == KeyEvent.ACTION_UP) {
- final Set<Integer> consumedKeys = mConsumedKeysForDevice.get(event.getDeviceId());
- return consumedKeys != null
- && consumedKeys.contains(event.getKeyCode());
- }
-
- KeyInterceptionInfo info =
- mWindowManagerInternal.getKeyInterceptionInfoFromToken(focusedToken);
-
- if (info == null || !mButtonOverridePermissionChecker.canWindowOverridePowerKey(mContext,
- info.windowOwnerUid, info.inputFeaturesFlags)) {
- // The focused window does not have the permission to override power key behavior.
- if (DEBUG_INPUT) {
- String interceptReason = "";
- if (info == null) {
- interceptReason = "Window is null";
- } else if (!mButtonOverridePermissionChecker.canAppOverrideSystemKey(mContext,
- info.windowOwnerUid)) {
- interceptReason = "Application does not have "
- + "OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW permission";
- } else {
- interceptReason = "Window does not have inputFeatureFlag set";
- }
-
- Log.d(TAG, String.format("Intercepting KEYCODE_POWER event. action=%d, "
- + "eventTime=%d to window=%s. interceptReason=%s. "
- + "mDoubleTapPowerDetected=%b",
- event.getAction(), event.getEventTime(), (info != null)
- ? info.windowTitle : "null", interceptReason,
- mDoubleTapPowerDetected));
- }
- // Intercept the key (i.e. do not send to app)
- setDeferredKeyActionsExecutableAsync(KEYCODE_POWER, event.getDownTime());
- return true;
- }
-
- if (DEBUG_INPUT) {
- Log.d(TAG, String.format("Sending KEYCODE_POWER to app. action=%d, "
- + "eventTime=%d to window=%s. mDoubleTapPowerDetected=%b",
- event.getAction(), event.getEventTime(), info.windowTitle,
- mDoubleTapPowerDetected));
- }
-
- if (!mDoubleTapPowerDetected) {
- //Single press: post a delayed runnable for the single press power action that will be
- // called if it's not cancelled by a double press.
- final var downTime = event.getDownTime();
- mPowerKeyDelayedRunnable = () ->
- setDeferredKeyActionsExecutableAsync(KEYCODE_POWER, downTime);
- mHandler.postDelayed(mPowerKeyDelayedRunnable, POWER_MULTI_PRESS_TIMEOUT_MILLIS);
- } else if (mPowerKeyDelayedRunnable != null) {
- //Double press detected: cancel the single press runnable.
- mHandler.removeCallbacks(mPowerKeyDelayedRunnable);
- mPowerKeyDelayedRunnable = null;
- }
-
- // Focused window has permission. Send to app.
- return false;
- }
-
@SuppressLint("MissingPermission")
private void initKeyGestures() {
if (!useKeyGestureEventHandler()) {
@@ -4790,11 +4631,6 @@
return true;
}
- if (overridePowerKeyBehaviorInFocusedWindow() && keyCode == KEYCODE_POWER) {
- handleUnhandledSystemKey(event);
- return true;
- }
-
if (useKeyGestureEventHandler()) {
return false;
}
@@ -5629,13 +5465,8 @@
KeyEvent.actionToString(event.getAction()),
mPowerKeyHandled ? 1 : 0,
mSingleKeyGestureDetector.getKeyPressCounter(KeyEvent.KEYCODE_POWER));
- if (overridePowerKeyBehaviorInFocusedWindow()) {
- result |= ACTION_PASS_TO_USER;
- } else {
- // Any activity on the power button stops the accessibility shortcut
- result &= ~ACTION_PASS_TO_USER;
- }
-
+ // Any activity on the power button stops the accessibility shortcut
+ result &= ~ACTION_PASS_TO_USER;
isWakeKey = false; // wake-up will be handled separately
if (down) {
interceptPowerKeyDown(event, interactiveAndAwake, isKeyGestureTriggered);
@@ -5897,32 +5728,6 @@
}
if (event.getKeyCode() == KEYCODE_POWER && event.getAction() == KeyEvent.ACTION_DOWN) {
- if (overridePowerKeyBehaviorInFocusedWindow()) {
- if (mGestureLauncherService != null) {
- mGestureLauncherService.processPowerKeyDown(event);
- }
-
- if (detectDoubleTapPower(event)) {
- mDoubleTapPowerDetected = true;
-
- // Copy of the event for handler in case the original event gets recycled.
- KeyEvent eventCopy = KeyEvent.obtain(event);
- mDeferredKeyActionExecutor.queueKeyAction(
- KeyEvent.KEYCODE_POWER,
- eventCopy.getEventTime(),
- () -> {
- if (!handleCameraGesture(eventCopy, interactive)) {
- mSingleKeyGestureDetector.interceptKey(
- eventCopy, interactive, defaultDisplayOn);
- } else {
- mSingleKeyGestureDetector.reset();
- }
- eventCopy.recycle();
- });
- return;
- }
- }
-
mPowerKeyHandled = handleCameraGesture(event, interactive);
if (mPowerKeyHandled) {
// handled by camera gesture.
@@ -5934,27 +5739,6 @@
mSingleKeyGestureDetector.interceptKey(event, interactive, defaultDisplayOn);
}
- private boolean detectDoubleTapPower(KeyEvent event) {
- if (event.getKeyCode() != KEYCODE_POWER || event.getAction() != KeyEvent.ACTION_DOWN) {
- return false;
- }
- if (event.isLongPress()) {
- return false;
- }
-
- final long powerTapInterval = event.getEventTime() - mLastPowerDown;
- mLastPowerDown = event.getEventTime();
- if (powerTapInterval >= POWER_MULTI_PRESS_TIMEOUT_MILLIS) {
- // Tap too slow for double press
- mPowerButtonConsecutiveTaps = 1;
- } else {
- mPowerButtonConsecutiveTaps++;
- }
-
- return powerTapInterval < POWER_MULTI_PRESS_TIMEOUT_MILLIS
- && mPowerButtonConsecutiveTaps == DOUBLE_POWER_TAP_COUNT_THRESHOLD;
- }
-
// The camera gesture will be detected by GestureLauncherService.
private boolean handleCameraGesture(KeyEvent event, boolean interactive) {
// camera gesture.
@@ -7811,12 +7595,6 @@
null)
== PERMISSION_GRANTED;
}
-
- boolean canWindowOverridePowerKey(Context context, int uid, int inputFeaturesFlags) {
- return canAppOverrideSystemKey(context, uid)
- && (inputFeaturesFlags & WindowManager.LayoutParams
- .INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS) != 0;
- }
}
private int getTargetDisplayIdForKeyEvent(KeyEvent event) {
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index e753ce8..1bed48a 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -163,6 +163,7 @@
private final RollbackPackageHealthObserver mPackageHealthObserver;
private final AppDataRollbackHelper mAppDataRollbackHelper;
private final Runnable mRunExpiration = this::runExpiration;
+ private final PackageWatchdog mPackageWatchdog;
// The # of milli-seconds to sleep for each received ACTION_PACKAGE_ENABLE_ROLLBACK.
// Used by #blockRollbackManager to test timeout in enabling rollbacks.
@@ -190,6 +191,7 @@
mPackageHealthObserver = new RollbackPackageHealthObserver(mContext);
mAppDataRollbackHelper = new AppDataRollbackHelper(mInstaller);
+ mPackageWatchdog = PackageWatchdog.getInstance(mContext);
// Kick off and start monitoring the handler thread.
HandlerThread handlerThread = new HandlerThread("RollbackManagerServiceHandler");
@@ -1249,12 +1251,12 @@
// should document in PackageInstaller.SessionParams#setEnableRollback
// After enabling and committing any rollback, observe packages and
// prepare to rollback if packages crashes too frequently.
- mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(),
- mRollbackLifetimeDurationInMillis);
+ mPackageWatchdog.startExplicitHealthCheck(mPackageHealthObserver,
+ rollback.getPackageNames(), mRollbackLifetimeDurationInMillis);
}
} else {
- mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(),
- mRollbackLifetimeDurationInMillis);
+ mPackageWatchdog.startExplicitHealthCheck(mPackageHealthObserver,
+ rollback.getPackageNames(), mRollbackLifetimeDurationInMillis);
}
runExpiration();
}
@@ -1317,7 +1319,7 @@
}
});
- PackageWatchdog.getInstance(mContext).dump(ipw);
+ mPackageWatchdog.dump(ipw);
}
@AnyThread
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java b/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java
index b9c8d3d..f51c25d 100644
--- a/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java
+++ b/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java
@@ -24,9 +24,14 @@
import android.content.Context;
import android.os.UserManager;
import android.security.advancedprotection.AdvancedProtectionFeature;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Slog;
+import java.util.ArrayList;
+import java.util.List;
+
/** @hide */
public final class DisallowCellular2GAdvancedProtectionHook extends AdvancedProtectionHook {
private static final String TAG = "AdvancedProtectionDisallowCellular2G";
@@ -35,11 +40,13 @@
new AdvancedProtectionFeature(FEATURE_ID_DISALLOW_CELLULAR_2G);
private final DevicePolicyManager mDevicePolicyManager;
private final TelephonyManager mTelephonyManager;
+ private final SubscriptionManager mSubscriptionManager;
public DisallowCellular2GAdvancedProtectionHook(@NonNull Context context, boolean enabled) {
super(context, enabled);
mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
mTelephonyManager = context.getSystemService(TelephonyManager.class);
+ mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
setPolicy(enabled);
}
@@ -50,14 +57,44 @@
return mFeature;
}
+ private static boolean isEmbeddedSubscriptionVisible(SubscriptionInfo subInfo) {
+ if (subInfo.isEmbedded()
+ && (subInfo.getProfileClass() == SubscriptionManager.PROFILE_CLASS_PROVISIONING
+ || (com.android.internal.telephony.flags.Flags.oemEnabledSatelliteFlag()
+ && subInfo.isOnlyNonTerrestrialNetwork()))) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private List<TelephonyManager> getActiveTelephonyManagers() {
+ List<TelephonyManager> telephonyManagers = new ArrayList<>();
+
+ for (SubscriptionInfo subInfo : mSubscriptionManager.getActiveSubscriptionInfoList()) {
+ if (isEmbeddedSubscriptionVisible(subInfo)) {
+ telephonyManagers.add(
+ mTelephonyManager.createForSubscriptionId(subInfo.getSubscriptionId()));
+ }
+ }
+
+ return telephonyManagers;
+ }
+
@Override
public boolean isAvailable() {
- return mTelephonyManager.isDataCapable();
+ for (TelephonyManager telephonyManager : getActiveTelephonyManagers()) {
+ if (telephonyManager.isDataCapable()
+ && telephonyManager.isRadioInterfaceCapabilitySupported(
+ mTelephonyManager.CAPABILITY_USES_ALLOWED_NETWORK_TYPES_BITMASK)) {
+ return true;
+ }
+ }
+
+ return false;
}
private void setPolicy(boolean enabled) {
- Slog.i(TAG, "setPolicy called with " + enabled);
-
if (enabled) {
Slog.d(TAG, "Setting DISALLOW_CELLULAR_2G_GLOBALLY restriction");
mDevicePolicyManager.addUserRestrictionGlobally(
@@ -75,12 +112,14 @@
// Leave 2G disabled even if APM is disabled.
if (!enabled) {
- long oldAllowedTypes =
- mTelephonyManager.getAllowedNetworkTypesForReason(
- TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G);
- long newAllowedTypes = oldAllowedTypes & ~TelephonyManager.NETWORK_CLASS_BITMASK_2G;
- mTelephonyManager.setAllowedNetworkTypesForReason(
- TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G, newAllowedTypes);
+ for (TelephonyManager telephonyManager : getActiveTelephonyManagers()) {
+ long oldAllowedTypes =
+ telephonyManager.getAllowedNetworkTypesForReason(
+ TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G);
+ long newAllowedTypes = oldAllowedTypes & ~TelephonyManager.NETWORK_CLASS_BITMASK_2G;
+ telephonyManager.setAllowedNetworkTypesForReason(
+ TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G, newAllowedTypes);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java
index b25656e..2ddbeab 100644
--- a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java
+++ b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java
@@ -24,42 +24,56 @@
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.security.intrusiondetection.IIntrusionDetectionEventTransport;
import android.security.intrusiondetection.IntrusionDetectionEvent;
+import android.security.intrusiondetection.IIntrusionDetectionEventTransport;
import android.text.TextUtils;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.infra.AndroidFuture;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.Process;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.concurrent.TimeUnit;
public class IntrusionDetectionEventTransportConnection implements ServiceConnection {
+ private static final String PRODUCTION_BUILD = "user";
+ private static final String PROPERTY_BUILD_TYPE = "ro.build.type";
+ private static final String PROPERTY_INTRUSION_DETECTION_SERVICE_NAME =
+ "intrusiondetection_service_name";
+ private static final long FUTURE_TIMEOUT_MILLIS = 60 * 1000; // 1 min
private static final String TAG = "IntrusionDetectionEventTransportConnection";
- private static final long FUTURE_TIMEOUT_MILLIS = 60 * 1000; // 1 mins
private final Context mContext;
private String mIntrusionDetectionEventTransportConfig;
volatile IIntrusionDetectionEventTransport mService;
+
public IntrusionDetectionEventTransportConnection(Context context) {
mContext = context;
- mService = null;
}
/**
* Initialize the IntrusionDetectionEventTransport binder service.
- * @return Whether the initialization succeed.
+ *
+ * @return Whether the initialization succeeds.
*/
public boolean initialize() {
+ Slog.d(TAG, "initialize");
if (!bindService()) {
return false;
}
+ // Wait for the service to be connected before calling initialize.
+ waitForConnection();
AndroidFuture<Boolean> resultFuture = new AndroidFuture<>();
try {
mService.initialize(resultFuture);
@@ -77,6 +91,20 @@
}
}
+ private void waitForConnection() {
+ synchronized (this) {
+ while (mService == null) {
+ Slog.d(TAG, "waiting for connection to service...");
+ try {
+ this.wait();
+ } catch (InterruptedException e) {
+ /* never interrupted */
+ }
+ }
+ Slog.d(TAG, "connected to service");
+ }
+ }
+
/**
* Add data to the IntrusionDetectionEventTransport binder service.
* @param data List of IntrusionDetectionEvent.
@@ -118,11 +146,38 @@
}
}
+ private String getSystemPropertyValue(String propertyName) {
+ String comamandString = "getprop " + propertyName;
+ try {
+ Process process = Runtime.getRuntime().exec(comamandString);
+ BufferedReader reader =
+ new BufferedReader(new InputStreamReader(process.getInputStream()));
+ String propertyValue = reader.readLine();
+ reader.close();
+ return propertyValue;
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to get system property value:", e);
+ return null;
+ }
+ }
+
private boolean bindService() {
- mIntrusionDetectionEventTransportConfig = mContext.getString(
- com.android.internal.R.string.config_intrusionDetectionEventTransport);
+ String buildType = getSystemPropertyValue(PROPERTY_BUILD_TYPE);
+ mIntrusionDetectionEventTransportConfig =
+ mContext.getString(
+ com.android.internal.R.string.config_intrusionDetectionEventTransport);
+
+ // If the build type is not production, and a property value is set, use it instead.
+ // This allows us to test the service with a different config.
+ if (!buildType.equals(PRODUCTION_BUILD)
+ && !TextUtils.isEmpty(
+ getSystemPropertyValue(PROPERTY_INTRUSION_DETECTION_SERVICE_NAME))) {
+ mIntrusionDetectionEventTransportConfig =
+ getSystemPropertyValue(PROPERTY_INTRUSION_DETECTION_SERVICE_NAME);
+ }
+
if (TextUtils.isEmpty(mIntrusionDetectionEventTransportConfig)) {
- Slog.e(TAG, "config_intrusionDetectionEventTransport is empty");
+ Slog.e(TAG, "Unable to find a valid config for the transport service");
return false;
}
@@ -163,11 +218,19 @@
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- mService = IIntrusionDetectionEventTransport.Stub.asInterface(service);
+ synchronized (this) {
+ mService = IIntrusionDetectionEventTransport.Stub.asInterface(service);
+ Slog.d(TAG, "connected to service");
+ this.notifyAll();
+ }
}
@Override
public void onServiceDisconnected(ComponentName name) {
- mService = null;
+ synchronized (this) {
+ mService = null;
+ Slog.d(TAG, "disconnected from service");
+ this.notifyAll();
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e190963..1f224e2 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2274,7 +2274,7 @@
if (shellTransitions) {
// Before setDisplayProjection is applied by the start transaction of transition,
// set the transform hint to avoid using surface in old rotation.
- getPendingTransaction().setFixedTransformHint(mSurfaceControl, rotation);
+ setFixedTransformHint(getPendingTransaction(), mSurfaceControl, rotation);
// The sync transaction should already contains setDisplayProjection, so unset the
// hint to restore the natural state when the transaction is applied.
transaction.unsetFixedTransformHint(mSurfaceControl);
@@ -2284,6 +2284,10 @@
mWmService.mRotationWatcherController.dispatchDisplayRotationChange(mDisplayId, rotation);
}
+ void setFixedTransformHint(Transaction t, SurfaceControl sc, int rotation) {
+ t.setFixedTransformHint(sc, (rotation + mDisplayInfo.installOrientation) % 4);
+ }
+
void configureDisplayPolicy() {
mRootWindowContainer.updateDisplayImePolicyCache();
mDisplayPolicy.updateConfigurationAndScreenSizeDependentBehaviors();
diff --git a/services/core/java/com/android/server/wm/SeamlessRotator.java b/services/core/java/com/android/server/wm/SeamlessRotator.java
index c20b858..8f0f686 100644
--- a/services/core/java/com/android/server/wm/SeamlessRotator.java
+++ b/services/core/java/com/android/server/wm/SeamlessRotator.java
@@ -56,7 +56,7 @@
mOldRotation = oldRotation;
mNewRotation = newRotation;
mApplyFixedTransformHint = applyFixedTransformationHint;
- mFixedTransformHint = oldRotation;
+ mFixedTransformHint = (oldRotation + info.installOrientation) % 4;
final boolean flipped = info.rotation == ROTATION_90 || info.rotation == ROTATION_270;
final int pH = flipped ? info.logicalWidth : info.logicalHeight;
final int pW = flipped ? info.logicalHeight : info.logicalWidth;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index f090ef1..4ed1206 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3425,6 +3425,7 @@
info.isTopActivityNoDisplay = top != null && top.isNoDisplay();
info.isSleeping = shouldSleepActivities();
info.isTopActivityTransparent = top != null && !top.fillsParent();
+ info.isActivityStackTransparent = !topTask.forAllActivities(r -> (r.occludesParent()));
info.lastNonFullscreenBounds = topTask.mLastNonFullscreenBounds;
final WindowState windowState = top != null
? top.findMainWindow(/* includeStartingApp= */ false) : null;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 2397e03..aa60f93 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3733,7 +3733,7 @@
&& !mTransitionController.useShellTransitionsRotation()) {
if (deltaRotation != Surface.ROTATION_0) {
updateSurfaceRotation(t, deltaRotation, null /* positionLeash */);
- getPendingTransaction().setFixedTransformHint(mSurfaceControl,
+ mDisplayContent.setFixedTransformHint(getPendingTransaction(), mSurfaceControl,
getWindowConfiguration().getDisplayRotation());
} else if (deltaRotation != mLastDeltaRotation) {
t.setMatrix(mSurfaceControl, 1, 0, 0, 1);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 447d443..cebe790 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5750,10 +5750,9 @@
|| mKeyInterceptionInfo.layoutParamsPrivateFlags != getAttrs().privateFlags
|| mKeyInterceptionInfo.layoutParamsType != getAttrs().type
|| mKeyInterceptionInfo.windowTitle != getWindowTag()
- || mKeyInterceptionInfo.windowOwnerUid != getOwningUid()
- || mKeyInterceptionInfo.inputFeaturesFlags != getAttrs().inputFeatures) {
+ || mKeyInterceptionInfo.windowOwnerUid != getOwningUid()) {
mKeyInterceptionInfo = new KeyInterceptionInfo(getAttrs().type, getAttrs().privateFlags,
- getWindowTag().toString(), getOwningUid(), getAttrs().inputFeatures);
+ getWindowTag().toString(), getOwningUid());
}
return mKeyInterceptionInfo;
}
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 004f406..832295a 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -629,7 +629,7 @@
.build();
t.setPosition(leash, mLastSurfacePosition.x, mLastSurfacePosition.y);
t.reparent(getSurfaceControl(), leash);
- getPendingTransaction().setFixedTransformHint(leash,
+ mDisplayContent.setFixedTransformHint(getPendingTransaction(), leash,
getWindowConfiguration().getDisplayRotation());
mFixedRotationTransformLeash = leash;
updateSurfaceRotation(t, rotation, mFixedRotationTransformLeash);
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 5a45730..0464230 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -68,6 +68,7 @@
#include <map>
#include <vector>
+#include "android_hardware_display_DisplayTopology.h"
#include "android_hardware_display_DisplayViewport.h"
#include "android_hardware_input_InputApplicationHandle.h"
#include "android_hardware_input_InputWindowHandle.h"
@@ -321,6 +322,8 @@
void setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray);
+ void setDisplayTopology(JNIEnv* env, jobject topologyGraph);
+
base::Result<std::unique_ptr<InputChannel>> createInputChannel(const std::string& name);
base::Result<std::unique_ptr<InputChannel>> createInputMonitor(ui::LogicalDisplayId displayId,
const std::string& name,
@@ -640,6 +643,11 @@
InputReaderConfiguration::Change::DISPLAY_INFO);
}
+void NativeInputManager::setDisplayTopology(JNIEnv* env, jobject topologyGraph) {
+ android_hardware_display_DisplayTopologyGraph_toNative(env, topologyGraph);
+ // TODO(b/367661489): Use the topology
+}
+
base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputChannel(
const std::string& name) {
ATRACE_CALL();
@@ -2092,6 +2100,12 @@
im->setDisplayViewports(env, viewportObjArray);
}
+static void nativeSetDisplayTopology(JNIEnv* env, jobject nativeImplObj,
+ jobject displayTopologyObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ im->setDisplayTopology(env, displayTopologyObj);
+}
+
static jint nativeGetScanCodeState(JNIEnv* env, jobject nativeImplObj, jint deviceId,
jint sourceMask, jint scanCode) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -3148,6 +3162,8 @@
{"start", "()V", (void*)nativeStart},
{"setDisplayViewports", "([Landroid/hardware/display/DisplayViewport;)V",
(void*)nativeSetDisplayViewports},
+ {"setDisplayTopology", "(Landroid/hardware/display/DisplayTopologyGraph;)V",
+ (void*)nativeSetDisplayTopology},
{"getScanCodeState", "(III)I", (void*)nativeGetScanCodeState},
{"getKeyCodeState", "(III)I", (void*)nativeGetKeyCodeState},
{"getSwitchState", "(III)I", (void*)nativeGetSwitchState},
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index df37ec3..09fd8d4 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -49,6 +49,7 @@
int register_android_server_HardwarePropertiesManagerService(JNIEnv* env);
int register_android_server_SyntheticPasswordManager(JNIEnv* env);
int register_android_hardware_display_DisplayViewport(JNIEnv* env);
+int register_android_hardware_display_DisplayTopology(JNIEnv* env);
int register_android_server_am_OomConnection(JNIEnv* env);
int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
int register_android_server_am_Freezer(JNIEnv* env);
@@ -114,6 +115,7 @@
register_android_server_storage_AppFuse(env);
register_android_server_SyntheticPasswordManager(env);
register_android_hardware_display_DisplayViewport(env);
+ register_android_hardware_display_DisplayTopology(env);
register_android_server_am_OomConnection(env);
register_android_server_am_CachedAppOptimizer(env);
register_android_server_am_Freezer(env);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index e4db4bd..543e32f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -672,10 +672,6 @@
}
}
- void saveToXml(TypedXmlSerializer serializer) throws IOException {
- mPolicyKey.saveToXml(serializer);
- }
-
@Nullable
static <V> PolicyDefinition<V> readFromXml(TypedXmlPullParser parser)
throws XmlPullParserException, IOException {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
index a4fa089..0d9dbaa 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
@@ -19,7 +19,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.admin.PolicyValue;
-import android.app.admin.flags.Flags;
import android.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;
@@ -41,7 +40,6 @@
private static final String TAG = "PolicyState";
private static final String TAG_ADMIN_POLICY_ENTRY = "admin-policy-entry";
- private static final String TAG_POLICY_DEFINITION_ENTRY = "policy-definition-entry";
private static final String TAG_RESOLVED_VALUE_ENTRY = "resolved-value-entry";
private static final String TAG_ENFORCING_ADMIN_ENTRY = "enforcing-admin-entry";
private static final String TAG_POLICY_VALUE_ENTRY = "policy-value-entry";
@@ -225,12 +223,6 @@
}
void saveToXml(TypedXmlSerializer serializer) throws IOException {
- if (!Flags.dontWritePolicyDefinition()) {
- serializer.startTag(/* namespace= */ null, TAG_POLICY_DEFINITION_ENTRY);
- mPolicyDefinition.saveToXml(serializer);
- serializer.endTag(/* namespace= */ null, TAG_POLICY_DEFINITION_ENTRY);
- }
-
if (mCurrentResolvedPolicy != null) {
serializer.startTag(/* namespace= */ null, TAG_RESOLVED_VALUE_ENTRY);
mPolicyDefinition.savePolicyValueToXml(
diff --git a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
index f5360eb..6b28047 100644
--- a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
+++ b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
@@ -213,7 +213,8 @@
}
private int getShareEventType(IntentFilter intentFilter) {
- String mimeType = intentFilter != null ? intentFilter.getDataType(0) : null;
+ String mimeType = (intentFilter != null && intentFilter.countDataTypes() > 0)
+ ? intentFilter.getDataType(0) : null;
return getDataManager().mimeTypeToShareEventType(mimeType);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index 66e9c98..238654d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -27,6 +27,7 @@
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -126,16 +127,11 @@
@Test
public void testConstructor_doesNotStartsLightSensorController() {
- verify(mMockLightSensorController, never()).restart();
- }
-
- @Test
- public void testConstructor_startsLightSensorController() {
when(mMockModifier.shouldListenToLightSensor()).thenReturn(true);
mClamperController = createBrightnessClamperController();
- verify(mMockLightSensorController).restart();
+ verify(mMockLightSensorController, never()).restart();
}
@Test
@@ -171,20 +167,43 @@
@Test
public void testOnDisplayChanged_doesNotRestartLightSensor() {
+ mClamperController.clamp(mDisplayBrightnessState, mMockRequest, 0.1f,
+ false, STATE_ON);
+ reset(mMockLightSensorController);
+
mClamperController.onDisplayChanged(mMockDisplayDeviceData);
verify(mMockLightSensorController, never()).restart();
+ verify(mMockLightSensorController).stop();
}
@Test
public void testOnDisplayChanged_restartsLightSensor() {
when(mMockModifier.shouldListenToLightSensor()).thenReturn(true);
+ mClamperController.clamp(mDisplayBrightnessState, mMockRequest, 0.1f,
+ false, STATE_ON);
+ reset(mMockLightSensorController);
+
mClamperController.onDisplayChanged(mMockDisplayDeviceData);
+ verify(mMockLightSensorController, never()).stop();
verify(mMockLightSensorController).restart();
}
@Test
+ public void testOnDisplayChanged_doesNotRestartLightSensor_screenOff() {
+ when(mMockModifier.shouldListenToLightSensor()).thenReturn(true);
+ mClamperController.clamp(mDisplayBrightnessState, mMockRequest, 0.1f,
+ false, STATE_OFF);
+ reset(mMockLightSensorController);
+
+ mClamperController.onDisplayChanged(mMockDisplayDeviceData);
+
+ verify(mMockLightSensorController, never()).restart();
+ verify(mMockLightSensorController).stop();
+ }
+
+ @Test
public void testClamp_AppliesModifier() {
float initialBrightness = 0.2f;
boolean initialSlowChange = true;
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
index d58e772..835705d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
@@ -188,8 +188,8 @@
}
@Test
- public void testDensityBasedCoarsening_ifFeatureIsEnabledButNotDefault_cacheIsNotUsed() {
- mSetFlagsRule.disableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ public void testDensityBasedCoarsening_ifFeatureIsEnabledButNoDefaultValue_cacheIsNotUsed() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
LocationFudgerCache cache = mock(LocationFudgerCache.class);
doReturn(false).when(cache).hasDefaultValue();
diff --git a/services/tests/security/intrusiondetection/AndroidManifest.xml b/services/tests/security/intrusiondetection/AndroidManifest.xml
index b30710d..6460a32 100644
--- a/services/tests/security/intrusiondetection/AndroidManifest.xml
+++ b/services/tests/security/intrusiondetection/AndroidManifest.xml
@@ -19,6 +19,7 @@
<uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING" />
<uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE" />
<application android:testOnly="true" android:debuggable="true" android:usesCleartextTraffic="true">
<uses-library android:name="android.test.runner"/>
diff --git a/services/tests/security/intrusiondetection/AndroidTest.xml b/services/tests/security/intrusiondetection/AndroidTest.xml
index 6489dea4a..0d21158 100644
--- a/services/tests/security/intrusiondetection/AndroidTest.xml
+++ b/services/tests/security/intrusiondetection/AndroidTest.xml
@@ -24,6 +24,10 @@
<option name="install-arg" value="-t" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="setprop intrusiondetection_service_name com.android.coretests.apps.testapp/.TestLoggingService" />
+ </target_preparer>
+
<option name="test-tag" value="IntrusionDetectionServiceTests" />
<test class="com.android.tradefed.testtype.InstrumentationTest" >
<option name="package" value="com.android.server.security.intrusiondetection.tests" />
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
index e505ebe..1380a6f 100644
--- a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.INTERNET;
import static android.Manifest.permission.MANAGE_INTRUSION_DETECTION_STATE;
import static android.Manifest.permission.READ_INTRUSION_DETECTION_STATE;
+import static android.Manifest.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -44,6 +45,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.Manifest;
import android.os.IBinder;
import android.os.Bundle;
import android.os.Looper;
@@ -159,6 +161,7 @@
mPermissionEnforcer = new FakePermissionEnforcer();
mPermissionEnforcer.grant(READ_INTRUSION_DETECTION_STATE);
mPermissionEnforcer.grant(MANAGE_INTRUSION_DETECTION_STATE);
+ mPermissionEnforcer.grant(BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE);
mTestLooper = new TestLooper();
mLooper = mTestLooper.getLooper();
@@ -578,6 +581,9 @@
}
@Test
+ @RequireRunOnSystemUser
+ @EnsureHasPermission(
+ android.Manifest.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE)
public void test_StartIntrusionDetectionEventTransportService() {
final String TAG = "test_StartIntrusionDetectionEventTransportService";
ServiceConnection serviceConnection = null;
@@ -639,6 +645,20 @@
return serviceConnection;
}
+ @Test
+ @RequireRunOnSystemUser
+ @EnsureHasPermission(
+ android.Manifest.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE)
+ public void testIntrusionDetectionEventTransportConnection_isValidAndBinds()
+ throws InterruptedException {
+ IntrusionDetectionEventTransportConnection intrusionDetectionEventTransportConnection =
+ new IntrusionDetectionEventTransportConnection(mContext);
+ // In a real scenario, the connection will be initialized by the service.
+ // Just to show that the connection is valid and able to bind,
+ // we initialize it here.
+ assertTrue(intrusionDetectionEventTransportConnection.initialize());
+ }
+
private class MockInjector implements IntrusionDetectionService.Injector {
private final Context mContext;
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/AndroidManifest.xml b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/AndroidManifest.xml
index 7cc75ab..a1a7e29 100644
--- a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/AndroidManifest.xml
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/AndroidManifest.xml
@@ -16,9 +16,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.coretests.apps.testapp">
+ <uses-permission android:name="android.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE" />
<application>
<service android:name=".TestLoggingService"
- android:exported="true" />
+ android:exported="true"
+ android:permission="android.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE" />
</application>
</manifest>
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
index 9850cb0..8024915 100644
--- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
@@ -16,12 +16,7 @@
package com.android.server;
-import static android.service.quickaccesswallet.Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP;
-import static android.service.quickaccesswallet.Flags.launchWalletOptionOnPowerDoubleTap;
-
-import static com.android.server.GestureLauncherService.LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER;
-import static com.android.server.GestureLauncherService.LAUNCH_WALLET_ON_DOUBLE_TAP_POWER;
-import static com.android.server.GestureLauncherService.POWER_DOUBLE_TAP_MAX_TIME_MS;
+import static com.android.server.GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -29,27 +24,19 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.PendingIntent;
import android.app.StatusBarManager;
-import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.res.Resources;
import android.os.Looper;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
-import android.service.quickaccesswallet.QuickAccessWalletClient;
import android.telecom.TelecomManager;
import android.test.mock.MockContentResolver;
import android.testing.TestableLooper;
@@ -68,7 +55,6 @@
import org.junit.Before;
import org.junit.BeforeClass;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -76,8 +62,6 @@
import org.mockito.MockitoAnnotations;
import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
/**
* Unit tests for {@link GestureLauncherService}.
@@ -106,32 +90,9 @@
private @Mock TelecomManager mTelecomManager;
private @Mock MetricsLogger mMetricsLogger;
@Mock private UiEventLogger mUiEventLogger;
- @Mock private QuickAccessWalletClient mQuickAccessWalletClient;
private MockContentResolver mContentResolver;
private GestureLauncherService mGestureLauncherService;
- private Context mInstrumentationContext =
- InstrumentationRegistry.getInstrumentation().getContext();
-
- private static final String LAUNCH_TEST_WALLET_ACTION = "LAUNCH_TEST_WALLET_ACTION";
- private static final String LAUNCH_FALLBACK_ACTION = "LAUNCH_FALLBACK_ACTION";
- private PendingIntent mGesturePendingIntent =
- PendingIntent.getBroadcast(
- mInstrumentationContext,
- 0,
- new Intent(LAUNCH_TEST_WALLET_ACTION),
- PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT);
-
- private PendingIntent mFallbackPendingIntent =
- PendingIntent.getBroadcast(
- mInstrumentationContext,
- 0,
- new Intent(LAUNCH_FALLBACK_ACTION),
- PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT);
-
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
@BeforeClass
public static void oneTimeInitialization() {
if (Looper.myLooper() == null) {
@@ -154,49 +115,9 @@
when(mContext.getContentResolver()).thenReturn(mContentResolver);
when(mContext.getSystemService(Context.TELECOM_SERVICE)).thenReturn(mTelecomManager);
when(mTelecomManager.createLaunchEmergencyDialerIntent(null)).thenReturn(new Intent());
- when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(true);
- mGestureLauncherService =
- new GestureLauncherService(
- mContext, mMetricsLogger, mQuickAccessWalletClient, mUiEventLogger);
-
- withDoubleTapPowerGestureEnableSettingValue(true);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
- }
-
- private WalletLaunchedReceiver registerWalletLaunchedReceiver(String action) {
- IntentFilter filter = new IntentFilter(action);
- WalletLaunchedReceiver receiver = new WalletLaunchedReceiver();
- mInstrumentationContext.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED);
- return receiver;
- }
-
- /**
- * A simple {@link BroadcastReceiver} implementation that counts down a {@link CountDownLatch}
- * when a matching message is received
- */
- private static final class WalletLaunchedReceiver extends BroadcastReceiver {
- private static final int TIMEOUT_SECONDS = 3;
-
- private final CountDownLatch mLatch;
-
- WalletLaunchedReceiver() {
- mLatch = new CountDownLatch(1);
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- mLatch.countDown();
- context.unregisterReceiver(this);
- }
-
- Boolean waitUntilShown() {
- try {
- return mLatch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- return false;
- }
- }
+ mGestureLauncherService = new GestureLauncherService(mContext, mMetricsLogger,
+ mUiEventLogger);
}
@Test
@@ -213,123 +134,37 @@
@Test
public void testIsCameraDoubleTapPowerSettingEnabled_configFalseSettingDisabled() {
- if (launchWalletOptionOnPowerDoubleTap()) {
- withDoubleTapPowerEnabledConfigValue(false);
- withDoubleTapPowerGestureEnableSettingValue(false);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
- } else {
- withCameraDoubleTapPowerEnableConfigValue(false);
- withCameraDoubleTapPowerDisableSettingValue(1);
- }
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ withCameraDoubleTapPowerDisableSettingValue(1);
assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
mContext, FAKE_USER_ID));
}
@Test
public void testIsCameraDoubleTapPowerSettingEnabled_configFalseSettingEnabled() {
- if (launchWalletOptionOnPowerDoubleTap()) {
- withDoubleTapPowerEnabledConfigValue(false);
- withDoubleTapPowerGestureEnableSettingValue(true);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
- assertTrue(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
- mContext, FAKE_USER_ID));
- } else {
- withCameraDoubleTapPowerEnableConfigValue(false);
- withCameraDoubleTapPowerDisableSettingValue(0);
- assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
- mContext, FAKE_USER_ID));
- }
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+ mContext, FAKE_USER_ID));
}
@Test
public void testIsCameraDoubleTapPowerSettingEnabled_configTrueSettingDisabled() {
- if (launchWalletOptionOnPowerDoubleTap()) {
- withDoubleTapPowerEnabledConfigValue(true);
- withDoubleTapPowerGestureEnableSettingValue(false);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
- } else {
- withCameraDoubleTapPowerEnableConfigValue(true);
- withCameraDoubleTapPowerDisableSettingValue(1);
- }
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(1);
assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
mContext, FAKE_USER_ID));
}
@Test
public void testIsCameraDoubleTapPowerSettingEnabled_configTrueSettingEnabled() {
- if (launchWalletOptionOnPowerDoubleTap()) {
- withDoubleTapPowerEnabledConfigValue(true);
- withDoubleTapPowerGestureEnableSettingValue(true);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
- } else {
- withCameraDoubleTapPowerEnableConfigValue(true);
- withCameraDoubleTapPowerDisableSettingValue(0);
- }
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
assertTrue(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
mContext, FAKE_USER_ID));
}
@Test
- @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
- public void testIsCameraDoubleTapPowerSettingEnabled_actionWallet() {
- withDoubleTapPowerEnabledConfigValue(true);
- withDoubleTapPowerGestureEnableSettingValue(true);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
-
- assertFalse(
- mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
- mContext, FAKE_USER_ID));
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
- public void testIsWalletDoubleTapPowerSettingEnabled() {
- withDoubleTapPowerEnabledConfigValue(true);
- withDoubleTapPowerGestureEnableSettingValue(true);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
-
- assertTrue(
- mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
- mContext, FAKE_USER_ID));
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
- public void testIsWalletDoubleTapPowerSettingEnabled_configDisabled() {
- withDoubleTapPowerEnabledConfigValue(false);
- withDoubleTapPowerGestureEnableSettingValue(true);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
-
- assertTrue(
- mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
- mContext, FAKE_USER_ID));
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
- public void testIsWalletDoubleTapPowerSettingEnabled_settingDisabled() {
- withDoubleTapPowerEnabledConfigValue(true);
- withDoubleTapPowerGestureEnableSettingValue(false);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
-
- assertFalse(
- mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
- mContext, FAKE_USER_ID));
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
- public void testIsWalletDoubleTapPowerSettingEnabled_actionCamera() {
- withDoubleTapPowerEnabledConfigValue(true);
- withDoubleTapPowerGestureEnableSettingValue(true);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
-
- assertFalse(
- mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
- mContext, FAKE_USER_ID));
- }
-
- @Test
public void testIsEmergencyGestureSettingEnabled_settingDisabled() {
withEmergencyGestureEnabledConfigValue(true);
withEmergencyGestureEnabledSettingValue(false);
@@ -410,9 +245,12 @@
@Test
public void testInterceptPowerKeyDown_firstPowerDownCameraPowerGestureOnInteractive() {
- enableCameraGesture();
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
- long eventTime = INITIAL_EVENT_TIME_MILLIS + POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ long eventTime = INITIAL_EVENT_TIME_MILLIS +
+ CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
boolean interactive = true;
@@ -446,12 +284,8 @@
@Test
public void testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOffInteractive() {
- if (launchWalletOptionOnPowerDoubleTap()) {
- withDoubleTapPowerGestureEnableSettingValue(false);
- } else {
- withCameraDoubleTapPowerEnableConfigValue(false);
- withCameraDoubleTapPowerDisableSettingValue(1);
- }
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ withCameraDoubleTapPowerDisableSettingValue(1);
mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
@@ -464,7 +298,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -495,12 +329,8 @@
@Test
public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOffInteractive() {
- if (launchWalletOptionOnPowerDoubleTap()) {
- withDoubleTapPowerGestureEnableSettingValue(false);
- } else {
- withCameraDoubleTapPowerEnableConfigValue(false);
- withCameraDoubleTapPowerDisableSettingValue(1);
- }
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ withCameraDoubleTapPowerDisableSettingValue(1);
mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
@@ -513,7 +343,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -592,7 +422,10 @@
@Test
public void
testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnInteractiveSetupComplete() {
- enableCameraGesture();
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ withUserSetupCompleteValue(true);
long eventTime = INITIAL_EVENT_TIME_MILLIS;
KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -604,7 +437,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -637,145 +470,15 @@
}
@Test
- @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
- public void
- testInterceptPowerKeyDown_fiveInboundPresses_walletAndEmergencyEnabled_bothLaunch() {
- WalletLaunchedReceiver receiver = registerWalletLaunchedReceiver(LAUNCH_TEST_WALLET_ACTION);
- setUpGetGestureTargetActivityPendingIntent(mGesturePendingIntent);
- enableEmergencyGesture();
- enableWalletGesture();
-
- // First event
- long eventTime = INITIAL_EVENT_TIME_MILLIS;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
-
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
- eventTime += interval;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, true);
-
- assertTrue(receiver.waitUntilShown());
-
- // Presses 3 and 4 should not trigger any gesture
- for (int i = 0; i < 2; i++) {
- eventTime += interval;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, false);
- }
-
- // Fifth button press should trigger the emergency flow
- eventTime += interval;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, true);
-
- verify(mUiEventLogger, times(1))
- .log(GestureLauncherService.GestureLauncherEvent.GESTURE_EMERGENCY_TAP_POWER);
- verify(mStatusBarManagerInternal).onEmergencyActionLaunchGestureDetected();
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
- public void testInterceptPowerKeyDown_intervalInBoundsWalletPowerGesture() {
- WalletLaunchedReceiver receiver = registerWalletLaunchedReceiver(LAUNCH_TEST_WALLET_ACTION);
- setUpGetGestureTargetActivityPendingIntent(mGesturePendingIntent);
- enableWalletGesture();
- enableEmergencyGesture();
-
- long eventTime = INITIAL_EVENT_TIME_MILLIS;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
- eventTime += interval;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, true);
- assertTrue(receiver.waitUntilShown());
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
- public void testInterceptPowerKeyDown_walletGestureOn_quickAccessWalletServiceUnavailable() {
- when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(false);
- WalletLaunchedReceiver receiver = registerWalletLaunchedReceiver(LAUNCH_TEST_WALLET_ACTION);
- setUpGetGestureTargetActivityPendingIntent(mGesturePendingIntent);
- enableWalletGesture();
-
- // First event
- long eventTime = INITIAL_EVENT_TIME_MILLIS;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
-
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
- eventTime += interval;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, false);
-
- assertFalse(receiver.waitUntilShown());
- }
-
- @Test
- public void testInterceptPowerKeyDown_walletGestureOn_userSetupIncomplete() {
- WalletLaunchedReceiver receiver = registerWalletLaunchedReceiver(LAUNCH_TEST_WALLET_ACTION);
- setUpGetGestureTargetActivityPendingIntent(mGesturePendingIntent);
- enableWalletGesture();
- withUserSetupCompleteValue(false);
-
- // First event
- long eventTime = INITIAL_EVENT_TIME_MILLIS;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
-
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
- eventTime += interval;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
-
- assertFalse(receiver.waitUntilShown());
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
- public void testInterceptPowerKeyDown_walletPowerGesture_nullPendingIntent() {
- WalletLaunchedReceiver gestureReceiver =
- registerWalletLaunchedReceiver(LAUNCH_TEST_WALLET_ACTION);
- setUpGetGestureTargetActivityPendingIntent(null);
- WalletLaunchedReceiver fallbackReceiver =
- registerWalletLaunchedReceiver(LAUNCH_FALLBACK_ACTION);
- setUpWalletFallbackPendingIntent(mFallbackPendingIntent);
- enableWalletGesture();
- enableEmergencyGesture();
-
- // First event
- long eventTime = INITIAL_EVENT_TIME_MILLIS;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
-
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
- eventTime += interval;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, true);
-
- assertFalse(gestureReceiver.waitUntilShown());
- assertTrue(fallbackReceiver.waitUntilShown());
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
- public void testInterceptPowerKeyDown_walletPowerGesture_intervalOutOfBounds() {
- WalletLaunchedReceiver gestureReceiver =
- registerWalletLaunchedReceiver(LAUNCH_TEST_WALLET_ACTION);
- setUpGetGestureTargetActivityPendingIntent(null);
- WalletLaunchedReceiver fallbackReceiver =
- registerWalletLaunchedReceiver(LAUNCH_FALLBACK_ACTION);
- setUpWalletFallbackPendingIntent(mFallbackPendingIntent);
- enableWalletGesture();
- enableEmergencyGesture();
-
- // First event
- long eventTime = INITIAL_EVENT_TIME_MILLIS;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
-
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS;
- eventTime += interval;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
-
- assertFalse(gestureReceiver.waitUntilShown());
- assertFalse(fallbackReceiver.waitUntilShown());
- }
-
- @Test
public void
testInterceptPowerKeyDown_fiveInboundPresses_cameraAndEmergencyEnabled_bothLaunch() {
- enableCameraGesture();
- enableEmergencyGesture();
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ withEmergencyGestureEnabledConfigValue(true);
+ withEmergencyGestureEnabledSettingValue(true);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ mGestureLauncherService.updateEmergencyGestureEnabled();
+ withUserSetupCompleteValue(true);
// First button press does nothing
long eventTime = INITIAL_EVENT_TIME_MILLIS;
@@ -788,7 +491,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
// 2nd button triggers camera
eventTime += interval;
@@ -877,7 +580,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
// 3 more button presses which should not trigger any gesture (camera gesture disabled)
for (int i = 0; i < 3; i++) {
eventTime += interval;
@@ -931,7 +634,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
// 3 more button presses which should not trigger any gesture, but intercepts action.
for (int i = 0; i < 3; i++) {
eventTime += interval;
@@ -1034,7 +737,7 @@
interactive, outLaunched);
assertTrue(intercepted);
assertFalse(outLaunched.value);
- interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
}
}
@@ -1062,7 +765,7 @@
interactive, outLaunched);
assertTrue(intercepted);
assertFalse(outLaunched.value);
- interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
}
}
@@ -1213,7 +916,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT, IGNORED_META_STATE, IGNORED_DEVICE_ID, IGNORED_SCANCODE,
@@ -1244,7 +947,9 @@
@Test
public void
testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnInteractiveSetupIncomplete() {
- enableCameraGesture();
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
withUserSetupCompleteValue(false);
long eventTime = INITIAL_EVENT_TIME_MILLIS;
@@ -1257,7 +962,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -1290,7 +995,9 @@
@Test
public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOnInteractive() {
- enableCameraGesture();
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1302,7 +1009,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -1335,7 +1042,9 @@
@Test
public void testInterceptPowerKeyDown_intervalOutOfBoundsCameraPowerGestureOnInteractive() {
- enableCameraGesture();
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1378,12 +1087,8 @@
@Test
public void testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOffNotInteractive() {
- if (launchWalletOptionOnPowerDoubleTap()) {
- withDoubleTapPowerGestureEnableSettingValue(false);
- } else {
- withCameraDoubleTapPowerEnableConfigValue(false);
- withCameraDoubleTapPowerDisableSettingValue(1);
- }
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ withCameraDoubleTapPowerDisableSettingValue(1);
mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
@@ -1396,7 +1101,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -1441,7 +1146,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -1518,7 +1223,10 @@
@Test
public void
testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnNotInteractiveSetupComplete() {
- enableCameraGesture();
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ withUserSetupCompleteValue(true);
long eventTime = INITIAL_EVENT_TIME_MILLIS;
KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1530,7 +1238,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -1564,7 +1272,9 @@
@Test
public void
testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnNotInteractiveSetupIncomplete() {
- enableCameraGesture();
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
withUserSetupCompleteValue(false);
long eventTime = INITIAL_EVENT_TIME_MILLIS;
@@ -1577,7 +1287,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -1622,7 +1332,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -1655,7 +1365,9 @@
@Test
public void testInterceptPowerKeyDown_intervalOutOfBoundsCameraPowerGestureOnNotInteractive() {
- enableCameraGesture();
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1697,53 +1409,12 @@
}
/**
- * If processPowerKeyDown is called instead of interceptPowerKeyDown (meaning the double tap
- * gesture isn't performed), the emergency gesture is still launched.
- */
- @Test
- public void
- testProcessPowerKeyDown_fiveInboundPresses_cameraDoesNotLaunch_emergencyGestureLaunches() {
- enableCameraGesture();
- enableEmergencyGesture();
-
- // First event
- long eventTime = INITIAL_EVENT_TIME_MILLIS;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
-
- //Second event; call processPowerKeyDown without calling interceptPowerKeyDown
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
- eventTime += interval;
- KeyEvent keyEvent =
- new KeyEvent(
- IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT);
- mGestureLauncherService.processPowerKeyDown(keyEvent);
-
- verify(mMetricsLogger, never())
- .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
- verify(mUiEventLogger, never()).log(any());
-
- // Presses 3 and 4 should not trigger any gesture
- for (int i = 0; i < 2; i++) {
- eventTime += interval;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, false);
- }
-
- // Fifth button press should still trigger the emergency flow
- eventTime += interval;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, true);
-
- verify(mUiEventLogger, times(1))
- .log(GestureLauncherService.GestureLauncherEvent.GESTURE_EMERGENCY_TAP_POWER);
- verify(mStatusBarManagerInternal).onEmergencyActionLaunchGestureDetected();
- }
-
- /**
* Helper method to trigger emergency gesture by pressing button for 5 times.
*
* @return last event time.
*/
private long triggerEmergencyGesture() {
- return triggerEmergencyGesture(POWER_DOUBLE_TAP_MAX_TIME_MS - 1);
+ return triggerEmergencyGesture(CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1);
}
/**
@@ -1802,27 +1473,6 @@
.thenReturn(enableConfigValue);
}
- private void withDoubleTapPowerEnabledConfigValue(boolean enable) {
- when(mResources.getBoolean(com.android.internal.R.bool.config_doubleTapPowerGestureEnabled))
- .thenReturn(enable);
- }
-
- private void withDoubleTapPowerGestureEnableSettingValue(boolean enable) {
- Settings.Secure.putIntForUser(
- mContentResolver,
- Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED,
- enable ? 1 : 0,
- UserHandle.USER_CURRENT);
- }
-
- private void withDefaultDoubleTapPowerGestureAction(int action) {
- Settings.Secure.putIntForUser(
- mContentResolver,
- Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE,
- action,
- UserHandle.USER_CURRENT);
- }
-
private void withEmergencyGestureEnabledConfigValue(boolean enableConfigValue) {
when(mResources.getBoolean(
com.android.internal.R.bool.config_emergencyGestureEnabled))
@@ -1860,72 +1510,4 @@
userSetupCompleteValue,
UserHandle.USER_CURRENT);
}
-
- private void setUpGetGestureTargetActivityPendingIntent(PendingIntent pendingIntent) {
- doAnswer(
- invocation -> {
- QuickAccessWalletClient.GesturePendingIntentCallback callback =
- (QuickAccessWalletClient.GesturePendingIntentCallback)
- invocation.getArguments()[1];
- callback.onGesturePendingIntentRetrieved(pendingIntent);
- return null;
- })
- .when(mQuickAccessWalletClient)
- .getGestureTargetActivityPendingIntent(any(), any());
- }
-
- private void setUpWalletFallbackPendingIntent(PendingIntent pendingIntent) {
- doAnswer(
- invocation -> {
- QuickAccessWalletClient.WalletPendingIntentCallback callback =
- (QuickAccessWalletClient.WalletPendingIntentCallback)
- invocation.getArguments()[1];
- callback.onWalletPendingIntentRetrieved(pendingIntent);
- return null;
- })
- .when(mQuickAccessWalletClient)
- .getWalletPendingIntent(any(), any());
- }
-
- private void enableWalletGesture() {
- withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
- withDoubleTapPowerGestureEnableSettingValue(true);
- withDoubleTapPowerEnabledConfigValue(true);
-
- mGestureLauncherService.updateWalletDoubleTapPowerEnabled();
- withUserSetupCompleteValue(true);
- }
-
- private void enableEmergencyGesture() {
- withEmergencyGestureEnabledConfigValue(true);
- withEmergencyGestureEnabledSettingValue(true);
- mGestureLauncherService.updateEmergencyGestureEnabled();
- withUserSetupCompleteValue(true);
- }
-
- private void enableCameraGesture() {
- if (launchWalletOptionOnPowerDoubleTap()) {
- withDoubleTapPowerEnabledConfigValue(true);
- withDoubleTapPowerGestureEnableSettingValue(true);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
- } else {
- withCameraDoubleTapPowerEnableConfigValue(true);
- withCameraDoubleTapPowerDisableSettingValue(0);
- }
- mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
- withUserSetupCompleteValue(true);
- }
-
- private void sendPowerKeyDownToGestureLauncherServiceAndAssertValues(
- long eventTime, boolean expectedIntercept, boolean expectedOutLaunchedValue) {
- KeyEvent keyEvent =
- new KeyEvent(
- IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT);
- boolean interactive = true;
- MutableBoolean outLaunched = new MutableBoolean(true);
- boolean intercepted =
- mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched);
- assertEquals(intercepted, expectedIntercept);
- assertEquals(outLaunched.value, expectedOutLaunchedValue);
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
index 27486b7..71a05f3 100644
--- a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
@@ -286,6 +286,25 @@
}
@Test
+ public void testPredictTargets_emptyIntentFilter() {
+ Bundle bundle = new Bundle();
+ IntentFilter filter = new IntentFilter();
+ bundle.putObject(ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY, filter);
+ AppPredictionContext predictionContext = new AppPredictionContext.Builder(mContext)
+ .setUiSurface(UI_SURFACE_SHARE)
+ .setPredictedTargetCount(NUM_PREDICTED_TARGETS)
+ .setExtras(bundle)
+ .build();
+ mPredictor = new ShareTargetPredictor(
+ predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID, mContext);
+
+ mPredictor.predictTargets();
+
+ verify(mUpdatePredictionsMethod).accept(mAppTargetCaptor.capture());
+ assertThat(mAppTargetCaptor.getValue()).isEmpty();
+ }
+
+ @Test
public void testPredictTargets_noSharingHistoryRankedByShortcutRank() {
mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc1", 3));
mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc2", 2));
diff --git a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
index be516e9..05a1482 100644
--- a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
@@ -18,22 +18,15 @@
import static android.view.KeyEvent.KEYCODE_POWER;
import static android.view.KeyEvent.KEYCODE_VOLUME_UP;
-import static com.android.hardware.input.Flags.FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_ASSISTANT;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_GLOBAL_ACTIONS;
-import static com.android.server.policy.PhoneWindowManager.POWER_MULTI_PRESS_TIMEOUT_MILLIS;
import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_POWER_DREAM_OR_SLEEP;
import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_POWER_GO_TO_SLEEP;
-import static org.junit.Assert.assertEquals;
-
-import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.view.Display;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
/**
@@ -46,12 +39,8 @@
@Before
public void setUp() {
setUpPhoneWindowManager();
- mPhoneWindowManager.overrideStatusBarManagerInternal();
}
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
/**
* Power single press to turn screen on/off.
*/
@@ -61,8 +50,6 @@
sendKey(KEYCODE_POWER);
mPhoneWindowManager.assertPowerSleep();
- mPhoneWindowManager.moveTimeForward(POWER_MULTI_PRESS_TIMEOUT_MILLIS);
-
// turn screen on when begin from non-interactive.
mPhoneWindowManager.overrideDisplayState(Display.STATE_OFF);
sendKey(KEYCODE_POWER);
@@ -103,7 +90,7 @@
mPhoneWindowManager.overrideCanStartDreaming(false);
sendKey(KEYCODE_POWER);
sendKey(KEYCODE_POWER);
- mPhoneWindowManager.assertDoublePowerLaunch();
+ mPhoneWindowManager.assertCameraLaunch();
mPhoneWindowManager.assertDidNotLockAfterAppTransitionFinished();
}
@@ -114,7 +101,7 @@
public void testPowerDoublePress() {
sendKey(KEYCODE_POWER);
sendKey(KEYCODE_POWER);
- mPhoneWindowManager.assertDoublePowerLaunch();
+ mPhoneWindowManager.assertCameraLaunch();
}
/**
@@ -127,8 +114,6 @@
sendKey(KEYCODE_POWER, true);
mPhoneWindowManager.assertSearchManagerLaunchAssist();
- mPhoneWindowManager.moveTimeForward(POWER_MULTI_PRESS_TIMEOUT_MILLIS);
-
// Show global actions.
mPhoneWindowManager.overrideLongPressOnPower(LONG_PRESS_POWER_GLOBAL_ACTIONS);
sendKey(KEYCODE_POWER, true);
@@ -156,139 +141,4 @@
sendKey(KEYCODE_POWER);
mPhoneWindowManager.assertNoPowerSleep();
}
-
- /**
- * Double press of power when the window handles the power key events. The
- * system double power gesture launch should not be performed.
- */
- @Test
- @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- public void testPowerDoublePress_windowHasOverridePermissionAndKeysHandled() {
- mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
- setDispatchedKeyHandler(keyEvent -> true);
-
- sendKey(KEYCODE_POWER);
- sendKey(KEYCODE_POWER);
-
- mPhoneWindowManager.assertDidNotLockAfterAppTransitionFinished();
-
- mPhoneWindowManager.assertNoDoublePowerLaunch();
- }
-
- /**
- * Double press of power when the window doesn't handle the power key events.
- * The system default gesture launch should be performed and the app should receive both events.
- */
- @Test
- @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- public void testPowerDoublePress_windowHasOverridePermissionAndKeysUnHandled() {
- mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
- setDispatchedKeyHandler(keyEvent -> false);
-
- sendKey(KEYCODE_POWER);
- sendKey(KEYCODE_POWER);
-
- mPhoneWindowManager.assertDidNotLockAfterAppTransitionFinished();
- mPhoneWindowManager.assertDoublePowerLaunch();
- assertEquals(getDownKeysDispatched(), 2);
- assertEquals(getUpKeysDispatched(), 2);
- }
-
- /**
- * Triple press of power when the window handles the power key double press gesture.
- * The system default gesture launch should not be performed, and the app only receives the
- * first two presses.
- */
- @Test
- @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- public void testPowerTriplePress_windowHasOverridePermissionAndKeysHandled() {
- mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
- setDispatchedKeyHandler(keyEvent -> true);
-
- sendKey(KEYCODE_POWER);
- sendKey(KEYCODE_POWER);
- sendKey(KEYCODE_POWER);
-
- mPhoneWindowManager.assertDidNotLockAfterAppTransitionFinished();
- mPhoneWindowManager.assertNoDoublePowerLaunch();
- assertEquals(getDownKeysDispatched(), 2);
- assertEquals(getUpKeysDispatched(), 2);
- }
-
- /**
- * Tests a single press, followed by a double press when the window can handle the power key.
- * The app should receive all 3 events.
- */
- @Test
- @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- public void testPowerTriplePressWithDelay_windowHasOverridePermissionAndKeysHandled() {
- mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
- setDispatchedKeyHandler(keyEvent -> true);
-
- sendKey(KEYCODE_POWER);
- mPhoneWindowManager.moveTimeForward(POWER_MULTI_PRESS_TIMEOUT_MILLIS);
- sendKey(KEYCODE_POWER);
- sendKey(KEYCODE_POWER);
-
- mPhoneWindowManager.assertNoDoublePowerLaunch();
- assertEquals(getDownKeysDispatched(), 3);
- assertEquals(getUpKeysDispatched(), 3);
- }
-
- /**
- * Tests single press when window doesn't handle the power key. Phone should go to sleep.
- */
- @Test
- @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- public void testPowerSinglePress_windowHasOverridePermissionAndKeyUnhandledByApp() {
- mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
- setDispatchedKeyHandler(keyEvent -> false);
- mPhoneWindowManager.overrideShortPressOnPower(SHORT_PRESS_POWER_GO_TO_SLEEP);
-
- sendKey(KEYCODE_POWER);
-
- mPhoneWindowManager.assertPowerSleep();
- }
-
- /**
- * Tests single press when the window handles the power key. Phone should go to sleep after a
- * delay of {POWER_MULTI_PRESS_TIMEOUT_MILLIS}
- */
- @Test
- @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- public void testPowerSinglePress_windowHasOverridePermissionAndKeyHandledByApp() {
- mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
- setDispatchedKeyHandler(keyEvent -> true);
- mPhoneWindowManager.overrideDisplayState(Display.STATE_ON);
- mPhoneWindowManager.overrideShortPressOnPower(SHORT_PRESS_POWER_GO_TO_SLEEP);
-
- sendKey(KEYCODE_POWER);
-
- mPhoneWindowManager.moveTimeForward(POWER_MULTI_PRESS_TIMEOUT_MILLIS);
-
- mPhoneWindowManager.assertPowerSleep();
- }
-
-
- /**
- * Tests 5x press when the window handles the power key. Emergency gesture should still be
- * launched.
- */
- @Test
- @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- public void testPowerFiveTimesPress_windowHasOverridePermissionAndKeyHandledByApp() {
- mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
- setDispatchedKeyHandler(keyEvent -> true);
- mPhoneWindowManager.overrideDisplayState(Display.STATE_ON);
- mPhoneWindowManager.overrideShortPressOnPower(SHORT_PRESS_POWER_GO_TO_SLEEP);
-
- for (int i = 0; i < 5; ++i) {
- sendKey(KEYCODE_POWER);
- mPhoneWindowManager.moveTimeForward(100);
- }
-
- mPhoneWindowManager.assertEmergencyLaunch();
- assertEquals(getDownKeysDispatched(), 2);
- assertEquals(getUpKeysDispatched(), 2);
- }
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index 5919501..9e47a00 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -85,11 +85,7 @@
private Resources mResources;
private PackageManager mPackageManager;
TestPhoneWindowManager mPhoneWindowManager;
-
- DispatchedKeyHandler mDispatchedKeyHandler;
- private int mDownKeysDispatched;
- private int mUpKeysDispatched;
-
+ DispatchedKeyHandler mDispatchedKeyHandler = event -> false;
Context mContext;
/** Modifier key to meta state */
@@ -120,9 +116,6 @@
XmlResourceParser testBookmarks = mResources.getXml(
com.android.frameworks.wmtests.R.xml.bookmarks);
doReturn(testBookmarks).when(mResources).getXml(com.android.internal.R.xml.bookmarks);
- mDispatchedKeyHandler = event -> false;
- mDownKeysDispatched = 0;
- mUpKeysDispatched = 0;
try {
// Keep packageName / className in sync with
@@ -285,14 +278,6 @@
doReturn(expectedBehavior).when(mResources).getInteger(eq(resId));
}
- int getDownKeysDispatched() {
- return mDownKeysDispatched;
- }
-
- int getUpKeysDispatched() {
- return mUpKeysDispatched;
- }
-
private void interceptKey(KeyEvent keyEvent) {
int actions = mPhoneWindowManager.interceptKeyBeforeQueueing(keyEvent);
if ((actions & ACTION_PASS_TO_USER) != 0) {
@@ -300,11 +285,6 @@
if (!mDispatchedKeyHandler.onKeyDispatched(keyEvent)) {
mPhoneWindowManager.interceptUnhandledKey(keyEvent);
}
- if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
- ++mDownKeysDispatched;
- } else {
- ++mUpKeysDispatched;
- }
}
}
mPhoneWindowManager.dispatchAllPendingEvents();
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index f06b45e..9db76d4 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -22,7 +22,6 @@
import static android.view.Display.STATE_ON;
import static android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE;
-import static com.android.hardware.input.Flags.overridePowerKeyBehaviorInFocusedWindow;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong;
@@ -46,14 +45,10 @@
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM;
import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAVIOR_MUTE;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.after;
-import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.description;
import static org.mockito.Mockito.mockingDetails;
import static org.mockito.Mockito.timeout;
@@ -90,9 +85,7 @@
import android.os.test.TestLooper;
import android.provider.Settings;
import android.service.dreams.DreamManagerInternal;
-import android.service.quickaccesswallet.QuickAccessWalletClient;
import android.telecom.TelecomManager;
-import android.util.MutableBoolean;
import android.view.Display;
import android.view.InputEvent;
import android.view.KeyCharacterMap;
@@ -102,12 +95,9 @@
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.internal.accessibility.AccessibilityShortcutController;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.KeyInterceptionInfo;
import com.android.server.GestureLauncherService;
import com.android.server.LocalServices;
-import com.android.server.SystemService;
import com.android.server.input.InputManagerInternal;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.pm.UserManagerInternal;
@@ -130,7 +120,6 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.quality.Strictness;
-import org.mockito.stubbing.Answer;
import java.util.List;
import java.util.function.Supplier;
@@ -143,8 +132,6 @@
private PhoneWindowManager mPhoneWindowManager;
private Context mContext;
- private GestureLauncherService mGestureLauncherService;
-
@Mock private WindowManagerInternal mWindowManagerInternal;
@Mock private ActivityManagerInternal mActivityManagerInternal;
@@ -176,9 +163,7 @@
@Mock private DisplayRotation mDisplayRotation;
@Mock private DisplayPolicy mDisplayPolicy;
@Mock private WindowManagerPolicy.ScreenOnListener mScreenOnListener;
- @Mock private QuickAccessWalletClient mQuickAccessWalletClient;
- @Mock private MetricsLogger mMetricsLogger;
- @Mock private UiEventLogger mUiEventLogger;
+ @Mock private GestureLauncherService mGestureLauncherService;
@Mock private GlobalActions mGlobalActions;
@Mock private AccessibilityShortcutController mAccessibilityShortcutController;
@@ -207,8 +192,6 @@
private int mKeyEventPolicyFlags = FLAG_INTERACTIVE;
- private int mProcessPowerKeyDownCount = 0;
-
private class TestTalkbackShortcutController extends TalkbackShortcutController {
TestTalkbackShortcutController(Context context) {
super(context);
@@ -277,8 +260,6 @@
MockitoAnnotations.initMocks(this);
mHandler = new Handler(mTestLooper.getLooper());
mContext = mockingDetails(context).isSpy() ? context : spy(context);
- mGestureLauncherService = spy(new GestureLauncherService(mContext, mMetricsLogger,
- mQuickAccessWalletClient, mUiEventLogger));
setUp(supportSettingsUpdate);
mTestLooper.dispatchAll();
}
@@ -291,7 +272,6 @@
mMockitoSession = mockitoSession()
.mockStatic(LocalServices.class, spyStubOnly)
.mockStatic(KeyCharacterMap.class)
- .mockStatic(GestureLauncherService.class)
.strictness(Strictness.LENIENT)
.startMocking();
@@ -314,16 +294,6 @@
() -> LocalServices.getService(eq(PowerManagerInternal.class)));
doReturn(mDisplayManagerInternal).when(
() -> LocalServices.getService(eq(DisplayManagerInternal.class)));
- doReturn(true).when(
- () -> GestureLauncherService.isCameraDoubleTapPowerSettingEnabled(any(), anyInt())
- );
- doReturn(true).when(
- () -> GestureLauncherService.isEmergencyGestureSettingEnabled(any(), anyInt())
- );
- doReturn(true).when(
- () -> GestureLauncherService.isGestureLauncherEnabled(any())
- );
- mGestureLauncherService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
doReturn(mGestureLauncherService).when(
() -> LocalServices.getService(eq(GestureLauncherService.class)));
doReturn(mUserManagerInternal).when(
@@ -405,8 +375,7 @@
doNothing().when(mContext).startActivityAsUser(any(), any());
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
- KeyInterceptionInfo interceptionInfo = new KeyInterceptionInfo(0, 0, null, 0,
- /* inputFeatureFlags = */ 0);
+ KeyInterceptionInfo interceptionInfo = new KeyInterceptionInfo(0, 0, null, 0);
doReturn(interceptionInfo)
.when(mWindowManagerInternal).getKeyInterceptionInfoFromToken(any());
@@ -424,8 +393,6 @@
eq(TEST_BROWSER_ROLE_PACKAGE_NAME));
doReturn(mSmsIntent).when(mPackageManager).getLaunchIntentForPackage(
eq(TEST_SMS_ROLE_PACKAGE_NAME));
- mProcessPowerKeyDownCount = 0;
- captureProcessPowerKeyDownCount();
Mockito.reset(mContext);
}
@@ -671,12 +638,6 @@
.when(mButtonOverridePermissionChecker).canAppOverrideSystemKey(any(), anyInt());
}
- void overrideCanWindowOverridePowerKey(boolean granted) {
- doReturn(granted)
- .when(mButtonOverridePermissionChecker).canWindowOverridePowerKey(any(), anyInt(),
- anyInt());
- }
-
void overrideKeyEventPolicyFlags(int flags) {
mKeyEventPolicyFlags = flags;
}
@@ -752,59 +713,13 @@
verify(mPowerManager, never()).goToSleep(anyLong(), anyInt(), anyInt());
}
- void assertDoublePowerLaunch() {
- ArgumentCaptor<MutableBoolean> valueCaptor = ArgumentCaptor.forClass(MutableBoolean.class);
-
+ void assertCameraLaunch() {
mTestLooper.dispatchAll();
- verify(mGestureLauncherService, atLeast(2))
- .interceptPowerKeyDown(any(), anyBoolean(), valueCaptor.capture());
- verify(mGestureLauncherService, atMost(4))
- .interceptPowerKeyDown(any(), anyBoolean(), valueCaptor.capture());
-
- if (overridePowerKeyBehaviorInFocusedWindow()) {
- assertTrue(mProcessPowerKeyDownCount >= 2 && mProcessPowerKeyDownCount <= 4);
- }
-
- List<Boolean> capturedValues = valueCaptor.getAllValues().stream()
- .map(mutableBoolean -> mutableBoolean.value)
- .toList();
-
- assertTrue(capturedValues.contains(true));
+ // GestureLauncherService should receive interceptPowerKeyDown twice.
+ verify(mGestureLauncherService, times(2))
+ .interceptPowerKeyDown(any(), anyBoolean(), any());
}
- void assertNoDoublePowerLaunch() {
- ArgumentCaptor<MutableBoolean> valueCaptor = ArgumentCaptor.forClass(MutableBoolean.class);
-
- mTestLooper.dispatchAll();
- verify(mGestureLauncherService, atLeast(0))
- .interceptPowerKeyDown(any(), anyBoolean(), valueCaptor.capture());
-
- List<Boolean> capturedValues = valueCaptor.getAllValues().stream()
- .map(mutableBoolean -> mutableBoolean.value)
- .toList();
-
- assertTrue(capturedValues.stream().noneMatch(value -> value));
- }
-
- void assertEmergencyLaunch() {
- ArgumentCaptor<MutableBoolean> valueCaptor = ArgumentCaptor.forClass(MutableBoolean.class);
-
- mTestLooper.dispatchAll();
- verify(mGestureLauncherService, atLeast(1))
- .interceptPowerKeyDown(any(), anyBoolean(), valueCaptor.capture());
-
- if (overridePowerKeyBehaviorInFocusedWindow()) {
- assertEquals(mProcessPowerKeyDownCount, 5);
- }
-
- List<Boolean> capturedValues = valueCaptor.getAllValues().stream()
- .map(mutableBoolean -> mutableBoolean.value)
- .toList();
-
- assertTrue(capturedValues.getLast());
- }
-
-
void assertSearchManagerLaunchAssist() {
mTestLooper.dispatchAll();
verify(mSearchManager).launchAssist(any());
@@ -1014,12 +929,4 @@
verify(mInputManagerInternal)
.handleKeyGestureInKeyGestureController(anyInt(), any(), anyInt(), eq(gestureType));
}
-
- private void captureProcessPowerKeyDownCount() {
- doAnswer((Answer<Void>) invocation -> {
- invocation.callRealMethod();
- mProcessPowerKeyDownCount++;
- return null;
- }).when(mGestureLauncherService).processPowerKeyDown(any());
- }
}
diff --git a/telephony/java/android/telephony/TelephonyDisplayInfo.java b/telephony/java/android/telephony/TelephonyDisplayInfo.java
index e01b10e..bb4ce6e 100644
--- a/telephony/java/android/telephony/TelephonyDisplayInfo.java
+++ b/telephony/java/android/telephony/TelephonyDisplayInfo.java
@@ -16,12 +16,15 @@
package android.telephony;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.Annotation.NetworkType;
import android.telephony.Annotation.OverrideNetworkType;
+import com.android.internal.telephony.flags.Flags;
+
import java.util.Objects;
/**
@@ -94,6 +97,12 @@
private final boolean mIsRoaming;
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ private final boolean mIsNtn;
+
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ private final boolean mIsSatelliteConstrainedData;
+
/**
* Constructor
*
@@ -106,7 +115,7 @@
@Deprecated
public TelephonyDisplayInfo(@NetworkType int networkType,
@OverrideNetworkType int overrideNetworkType) {
- this(networkType, overrideNetworkType, false);
+ this(networkType, overrideNetworkType, false, false, false);
}
/**
@@ -118,12 +127,37 @@
*
* @hide
*/
+ @Deprecated
public TelephonyDisplayInfo(@NetworkType int networkType,
@OverrideNetworkType int overrideNetworkType,
boolean isRoaming) {
mNetworkType = networkType;
mOverrideNetworkType = overrideNetworkType;
mIsRoaming = isRoaming;
+ mIsNtn = false;
+ mIsSatelliteConstrainedData = false;
+ }
+
+ /**
+ * Constructor
+ *
+ * @param networkType Current packet-switching cellular network type
+ * @param overrideNetworkType The override network type
+ * @param isRoaming True if the device is roaming after override.
+ * @param isNtn True if the device is camped to non-terrestrial network.
+ * @param isSatelliteConstrainedData True if the device satellite internet is bandwidth
+ * constrained.
+ *
+ * @hide
+ */
+ public TelephonyDisplayInfo(@NetworkType int networkType,
+ @OverrideNetworkType int overrideNetworkType,
+ boolean isRoaming, boolean isNtn, boolean isSatelliteConstrainedData) {
+ mNetworkType = networkType;
+ mOverrideNetworkType = overrideNetworkType;
+ mIsRoaming = isRoaming;
+ mIsNtn = isNtn;
+ mIsSatelliteConstrainedData = isSatelliteConstrainedData;
}
/** @hide */
@@ -131,6 +165,8 @@
mNetworkType = p.readInt();
mOverrideNetworkType = p.readInt();
mIsRoaming = p.readBoolean();
+ mIsNtn = p.readBoolean();
+ mIsSatelliteConstrainedData = p.readBoolean();
}
/**
@@ -170,11 +206,34 @@
return mIsRoaming;
}
+ /**
+ * Get whether the satellite internet is with bandwidth constrained capability set.
+ *
+ * @return {@code true} if satellite internet is connected with bandwidth constrained
+ * capability else {@code false}.
+ * @hide
+ */
+ public boolean isSatelliteConstrainedData() {
+ return mIsSatelliteConstrainedData;
+ }
+
+ /**
+ * Get whether the network is a non-terrestrial network.
+ *
+ * @return {@code true} if network is a non-terrestrial network else {@code false}.
+ * @hide
+ */
+ public boolean isNtn() {
+ return mIsNtn;
+ }
+
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mNetworkType);
dest.writeInt(mOverrideNetworkType);
dest.writeBoolean(mIsRoaming);
+ dest.writeBoolean(mIsNtn);
+ dest.writeBoolean(mIsSatelliteConstrainedData);
}
public static final @NonNull Parcelable.Creator<TelephonyDisplayInfo> CREATOR =
@@ -202,12 +261,15 @@
TelephonyDisplayInfo that = (TelephonyDisplayInfo) o;
return mNetworkType == that.mNetworkType
&& mOverrideNetworkType == that.mOverrideNetworkType
- && mIsRoaming == that.mIsRoaming;
+ && mIsRoaming == that.mIsRoaming
+ && mIsNtn == that.mIsNtn
+ && mIsSatelliteConstrainedData == that.mIsSatelliteConstrainedData;
}
@Override
public int hashCode() {
- return Objects.hash(mNetworkType, mOverrideNetworkType, mIsRoaming);
+ return Objects.hash(mNetworkType, mOverrideNetworkType, mIsRoaming, mIsNtn,
+ mIsSatelliteConstrainedData);
}
/**
@@ -233,6 +295,8 @@
public String toString() {
return "TelephonyDisplayInfo {network=" + TelephonyManager.getNetworkTypeName(mNetworkType)
+ ", overrideNetwork=" + overrideNetworkTypeToString(mOverrideNetworkType)
- + ", isRoaming=" + mIsRoaming + "}";
+ + ", isRoaming=" + mIsRoaming
+ + ", isNtn=" + mIsNtn
+ + ", isSatelliteConstrainedData=" + mIsSatelliteConstrainedData + "}";
}
}
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 038c6d7..43844f6 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -541,8 +541,7 @@
0
},
"title",
- /* uid = */0,
- /* inputFeatureFlags = */ 0
+ /* uid = */0
)
whenever(windowManagerInternal.getKeyInterceptionInfoFromToken(any())).thenReturn(info)
}
diff --git a/tests/NetworkSecurityConfigTest/res/xml/ct_domains.xml b/tests/NetworkSecurityConfigTest/res/xml/ct_domains.xml
new file mode 100644
index 0000000..67d4397a
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/ct_domains.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <certificateTransparency enabled="true" />
+ </base-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <trust-anchors>
+ <certificates src="system" />
+ </trust-anchors>
+ </domain-config>
+ <domain-config>
+ <domain>subdomain_user.android.com</domain>
+ <trust-anchors>
+ <certificates src="user" />
+ </trust-anchors>
+ </domain-config>
+ <domain-config>
+ <certificateTransparency enabled="true" />
+ <domain>subdomain_user_ct.android.com</domain>
+ <trust-anchors>
+ <certificates src="user" />
+ </trust-anchors>
+ </domain-config>
+ <domain-config>
+ <domain>subdomain_inline.android.com</domain>
+ <trust-anchors>
+ <certificates src="@raw/ca_certs_pem" />
+ </trust-anchors>
+ </domain-config>
+ <domain-config>
+ <certificateTransparency enabled="true" />
+ <domain>subdomain_inline_ct.android.com</domain>
+ <trust-anchors>
+ <certificates src="@raw/ca_certs_pem" />
+ </trust-anchors>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/ct_users.xml b/tests/NetworkSecurityConfigTest/res/xml/ct_users.xml
new file mode 100644
index 0000000..c35fd71
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/ct_users.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <trust-anchors>
+ <certificates src="user" />
+ </trust-anchors>
+ </base-config>
+ <domain-config>
+ <domain>android.com</domain>
+ </domain-config>
+ <domain-config>
+ <certificateTransparency enabled="true" />
+ <domain>subdomain.android.com</domain>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
index 0494f17..c6fe068 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
@@ -111,7 +111,8 @@
private NetworkSecurityConfig getSystemStoreConfig() {
return new NetworkSecurityConfig.Builder()
.addCertificatesEntryRef(
- new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
+ new CertificatesEntryRef(
+ SystemCertificateSource.getInstance(), false, false))
.build();
}
@@ -141,7 +142,8 @@
NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder()
.setPinSet(new PinSet(pins, Long.MAX_VALUE))
.addCertificatesEntryRef(
- new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
+ new CertificatesEntryRef(
+ SystemCertificateSource.getInstance(), false, false))
.build();
ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
@@ -159,7 +161,8 @@
NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder()
.setPinSet(new PinSet(pins, Long.MAX_VALUE))
.addCertificatesEntryRef(
- new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
+ new CertificatesEntryRef(
+ SystemCertificateSource.getInstance(), false, false))
.build();
ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
@@ -178,7 +181,8 @@
NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder()
.setPinSet(new PinSet(pins, Long.MAX_VALUE))
.addCertificatesEntryRef(
- new CertificatesEntryRef(SystemCertificateSource.getInstance(), true))
+ new CertificatesEntryRef(
+ SystemCertificateSource.getInstance(), true, false))
.build();
ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
@@ -245,7 +249,8 @@
NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder()
.setPinSet(new PinSet(pins, Long.MAX_VALUE))
.addCertificatesEntryRef(
- new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
+ new CertificatesEntryRef(
+ SystemCertificateSource.getInstance(), false, false))
.build();
ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
index 81e05c1..542465d 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
@@ -502,4 +502,47 @@
TestUtils.assertConnectionSucceeds(context, "android.com", 443);
TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
}
+
+ public void testCertificateTransparencyDomainConfig() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.ct_domains,
+ TestUtils.makeApplicationInfo());
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ assertTrue(appConfig.hasPerDomainConfigs());
+ NetworkSecurityConfig config = appConfig.getConfigForHostname("");
+ assertNotNull(config);
+ // Check defaults.
+ assertTrue(config.isCertificateTransparencyVerificationRequired());
+
+ config = appConfig.getConfigForHostname("android.com");
+ assertTrue(config.isCertificateTransparencyVerificationRequired());
+
+ config = appConfig.getConfigForHostname("subdomain_user.android.com");
+ assertFalse(config.isCertificateTransparencyVerificationRequired());
+
+ config = appConfig.getConfigForHostname("subdomain_user_ct.android.com");
+ assertTrue(config.isCertificateTransparencyVerificationRequired());
+
+ config = appConfig.getConfigForHostname("subdomain_inline.android.com");
+ assertFalse(config.isCertificateTransparencyVerificationRequired());
+
+ config = appConfig.getConfigForHostname("subdomain_inline_ct.android.com");
+ assertTrue(config.isCertificateTransparencyVerificationRequired());
+ }
+
+ public void testCertificateTransparencyUserConfig() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.ct_users,
+ TestUtils.makeApplicationInfo());
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ assertTrue(appConfig.hasPerDomainConfigs());
+ NetworkSecurityConfig config = appConfig.getConfigForHostname("");
+ assertNotNull(config);
+ // Check defaults.
+ assertFalse(config.isCertificateTransparencyVerificationRequired());
+
+ config = appConfig.getConfigForHostname("android.com");
+ assertFalse(config.isCertificateTransparencyVerificationRequired());
+
+ config = appConfig.getConfigForHostname("subdomain.android.com");
+ assertTrue(config.isCertificateTransparencyVerificationRequired());
+ }
}
diff --git a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
index af87bf7..49616c3 100644
--- a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
@@ -83,6 +83,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@@ -112,6 +113,7 @@
private final TestClock mTestClock = new TestClock();
private TestLooper mTestLooper;
+ private Executor mTestExecutor;
private Context mSpyContext;
// Keep track of all created watchdogs to apply device config changes
private List<PackageWatchdog> mAllocatedWatchdogs;
@@ -141,6 +143,7 @@
Manifest.permission.WRITE_DEVICE_CONFIG,
Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG);
mTestLooper = new TestLooper();
+ mTestExecutor = mTestLooper.getNewExecutor();
mSpyContext = spy(InstrumentationRegistry.getContext());
when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).then(inv -> {
@@ -231,31 +234,37 @@
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(2);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(3);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(4);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(4);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(5);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(5);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(6);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(6);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(7);
}
@@ -272,6 +281,7 @@
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
verify(rollbackObserver).onExecuteBootLoopMitigation(1);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
@@ -281,6 +291,7 @@
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rollbackObserver).onExecuteBootLoopMitigation(2);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
@@ -289,6 +300,7 @@
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
}
@@ -305,18 +317,21 @@
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(1);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(2);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3);
verify(rollbackObserver).onExecuteBootLoopMitigation(1);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
@@ -326,24 +341,28 @@
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(3);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(4);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(4);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(5);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(5);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(6);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(6);
verify(rollbackObserver).onExecuteBootLoopMitigation(2);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
@@ -352,6 +371,7 @@
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(6);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(7);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
@@ -362,6 +382,7 @@
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
}
@@ -379,12 +400,14 @@
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(1);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
verify(rollbackObserver).onExecuteBootLoopMitigation(1);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
@@ -394,6 +417,7 @@
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
verify(rollbackObserver).onExecuteBootLoopMitigation(2);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
@@ -402,6 +426,7 @@
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(2);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
@@ -412,6 +437,7 @@
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
}
@@ -739,14 +765,14 @@
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(e);
}
- watchdog.registerHealthObserver(rollbackObserver);
+ watchdog.registerHealthObserver(rollbackObserver, mTestExecutor);
return rollbackObserver;
}
RescuePartyObserver setUpRescuePartyObserver(PackageWatchdog watchdog) {
setCrashRecoveryPropRescueBootCount(0);
RescuePartyObserver rescuePartyObserver = spy(RescuePartyObserver.getInstance(mSpyContext));
assertFalse(RescueParty.isRebootPropertySet());
- watchdog.registerHealthObserver(rescuePartyObserver);
+ watchdog.registerHealthObserver(rescuePartyObserver, mTestExecutor);
return rescuePartyObserver;
}
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 5a8a6be..c64dc72 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -53,6 +53,7 @@
import android.provider.DeviceConfig;
import android.util.AtomicFile;
import android.util.LongArrayQueue;
+import android.util.Slog;
import android.util.Xml;
import androidx.test.InstrumentationRegistry;
@@ -88,6 +89,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -119,6 +121,7 @@
private final TestClock mTestClock = new TestClock();
private TestLooper mTestLooper;
+ private Executor mTestExecutor;
private Context mSpyContext;
// Keep track of all created watchdogs to apply device config changes
private List<PackageWatchdog> mAllocatedWatchdogs;
@@ -155,6 +158,7 @@
Manifest.permission.WRITE_DEVICE_CONFIG,
Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG);
mTestLooper = new TestLooper();
+ mTestExecutor = mTestLooper.getNewExecutor();
mSpyContext = spy(InstrumentationRegistry.getContext());
when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).then(inv -> {
@@ -226,7 +230,8 @@
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -242,8 +247,10 @@
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
new VersionedPackage(APP_B, VERSION_CODE)),
@@ -260,7 +267,8 @@
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
watchdog.unregisterHealthObserver(observer);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -276,8 +284,10 @@
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
watchdog.unregisterHealthObserver(observer2);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -294,7 +304,8 @@
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
moveTimeForwardAndDispatch(SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -310,8 +321,10 @@
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), LONG_DURATION);
moveTimeForwardAndDispatch(SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -330,13 +343,14 @@
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
// Start observing APP_A
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
// Then advance time half-way
moveTimeForwardAndDispatch(SHORT_DURATION / 2);
// Start observing APP_A again
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
// Then advance time such that it should have expired were it not for the second observation
moveTimeForwardAndDispatch((SHORT_DURATION / 2) + 1);
@@ -358,15 +372,17 @@
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog1.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog1.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
+ watchdog1.registerHealthObserver(observer1, mTestExecutor);
+ watchdog1.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog1.registerHealthObserver(observer2, mTestExecutor);
+ watchdog1.startExplicitHealthCheck(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
// Then advance time and run IO Handler so file is saved
mTestLooper.dispatchAll();
// Then start a new watchdog
PackageWatchdog watchdog2 = createWatchdog();
// Then resume observer1 and observer2
- watchdog2.registerHealthObserver(observer1);
- watchdog2.registerHealthObserver(observer2);
+ watchdog2.registerHealthObserver(observer1, mTestExecutor);
+ watchdog2.registerHealthObserver(observer2, mTestExecutor);
raiseFatalFailureAndDispatch(watchdog2,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
new VersionedPackage(APP_B, VERSION_CODE)),
@@ -374,6 +390,7 @@
// We should receive failed packages as expected to ensure observers are persisted and
// resumed correctly
+ mTestLooper.dispatchAll();
assertThat(observer1.mHealthCheckFailedPackages).containsExactly(APP_A);
assertThat(observer2.mHealthCheckFailedPackages).containsExactly(APP_A, APP_B);
}
@@ -387,8 +404,10 @@
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
// Then fail APP_A below the threshold
for (int i = 0; i < watchdog.getTriggerFailureCount() - 1; i++) {
@@ -414,9 +433,10 @@
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
-
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_B), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_B), SHORT_DURATION);
// Then fail APP_C (not observed) above the threshold
raiseFatalFailureAndDispatch(watchdog,
@@ -448,7 +468,8 @@
}
};
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
// Then fail APP_A (different version) above the threshold
raiseFatalFailureAndDispatch(watchdog,
@@ -477,13 +498,17 @@
PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
// Start observing for all impact observers
- watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
+ watchdog.registerHealthObserver(observerNone, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
SHORT_DURATION);
- watchdog.startObservingHealth(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
+ watchdog.registerHealthObserver(observerHigh, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
SHORT_DURATION);
- watchdog.startObservingHealth(observerMid, Arrays.asList(APP_A, APP_B),
+ watchdog.registerHealthObserver(observerMid, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerMid, Arrays.asList(APP_A, APP_B),
SHORT_DURATION);
- watchdog.startObservingHealth(observerLow, Arrays.asList(APP_A),
+ watchdog.registerHealthObserver(observerLow, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerLow, Arrays.asList(APP_A),
SHORT_DURATION);
// Then fail all apps above the threshold
@@ -523,13 +548,17 @@
PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
// Start observing for all impact observers
- watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
+ watchdog.registerHealthObserver(observerNone, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
SHORT_DURATION);
- watchdog.startObservingHealth(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
+ watchdog.registerHealthObserver(observerHigh, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
SHORT_DURATION);
- watchdog.startObservingHealth(observerMid, Arrays.asList(APP_A, APP_B),
+ watchdog.registerHealthObserver(observerMid, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerMid, Arrays.asList(APP_A, APP_B),
SHORT_DURATION);
- watchdog.startObservingHealth(observerLow, Arrays.asList(APP_A),
+ watchdog.registerHealthObserver(observerLow, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerLow, Arrays.asList(APP_A),
SHORT_DURATION);
// Then fail all apps above the threshold
@@ -577,8 +606,10 @@
PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
// Start observing for observerFirst and observerSecond with failure handling
- watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
- watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observerFirst, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observerSecond, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
// Then fail APP_A above the threshold
raiseFatalFailureAndDispatch(watchdog,
@@ -641,8 +672,10 @@
PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
// Start observing for observerFirst and observerSecond with failure handling
- watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
- watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observerFirst, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observerSecond, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
// Then fail APP_A above the threshold
raiseFatalFailureAndDispatch(watchdog,
@@ -709,8 +742,10 @@
PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
// Start observing for observer1 and observer2 with failure handling
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
// Then fail APP_A above the threshold
raiseFatalFailureAndDispatch(watchdog,
@@ -731,8 +766,10 @@
PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
// Start observing for observer1 and observer2 with failure handling
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
// Then fail APP_A above the threshold
raiseFatalFailureAndDispatch(watchdog,
@@ -762,8 +799,10 @@
// Start observing with explicit health checks for APP_A and APP_B respectively
// with observer1 and observer2
controller.setSupportedPackages(Arrays.asList(APP_A, APP_B));
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_B), SHORT_DURATION);
// Run handler so requests are dispatched to the controller
mTestLooper.dispatchAll();
@@ -779,7 +818,8 @@
// Observer3 didn't exist when we got the explicit health check above, so
// it starts out with a non-passing explicit health check and has to wait for a pass
// otherwise it would be notified of APP_A failure on expiry
- watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer3, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer3, Arrays.asList(APP_A), SHORT_DURATION);
// Then expire observers
moveTimeForwardAndDispatch(SHORT_DURATION);
@@ -809,8 +849,9 @@
// Start observing with explicit health checks for APP_A and APP_B
controller.setSupportedPackages(Arrays.asList(APP_A, APP_B, APP_C));
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_B), LONG_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_B), LONG_DURATION);
// Run handler so requests are dispatched to the controller
mTestLooper.dispatchAll();
@@ -846,7 +887,7 @@
// Then set new supported packages
controller.setSupportedPackages(Arrays.asList(APP_C));
// Start observing APP_A and APP_C; only APP_C has support for explicit health checks
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_C), SHORT_DURATION);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A, APP_C), SHORT_DURATION);
// Run handler so requests/cancellations are dispatched to the controller
mTestLooper.dispatchAll();
@@ -877,7 +918,8 @@
// package observation duration == LONG_DURATION
// health check duration == SHORT_DURATION (set by default in the TestController)
controller.setSupportedPackages(Arrays.asList(APP_A));
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), LONG_DURATION);
// Then APP_A has exceeded health check duration
moveTimeForwardAndDispatch(SHORT_DURATION);
@@ -908,7 +950,8 @@
// package observation duration == SHORT_DURATION / 2
// health check duration == SHORT_DURATION (set by default in the TestController)
controller.setSupportedPackages(Arrays.asList(APP_A));
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION / 2);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION / 2);
// Forward time to expire the observation duration
moveTimeForwardAndDispatch(SHORT_DURATION / 2);
@@ -981,7 +1024,7 @@
// Start observing with failure handling
TestObserver observer = new TestObserver(OBSERVER_NAME_1,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
- wd.startObservingHealth(observer, Collections.singletonList(APP_A), SHORT_DURATION);
+ wd.startExplicitHealthCheck(observer, Collections.singletonList(APP_A), SHORT_DURATION);
// Notify of NetworkStack failure
mConnectivityModuleCallbackCaptor.getValue().onNetworkStackFailure(APP_A);
@@ -1001,7 +1044,7 @@
// Start observing with failure handling
TestObserver observer = new TestObserver(OBSERVER_NAME_1,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
- wd.startObservingHealth(observer, Collections.singletonList(APP_A), SHORT_DURATION);
+ wd.startExplicitHealthCheck(observer, Collections.singletonList(APP_A), SHORT_DURATION);
// Notify of NetworkStack failure
mConnectivityModuleCallbackCaptor.getValue().onNetworkStackFailure(APP_A);
@@ -1022,7 +1065,8 @@
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
// Fail APP_A below the threshold which should not trigger package failures
for (int i = 0; i < PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT - 1; i++) {
watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -1050,7 +1094,8 @@
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_B), Long.MAX_VALUE);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A, APP_B), Long.MAX_VALUE);
watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS + 1);
@@ -1075,15 +1120,16 @@
}
/**
- * Test default monitoring duration is used when PackageWatchdog#startObservingHealth is offered
- * an invalid durationMs.
+ * Test default monitoring duration is used when PackageWatchdog#startExplicitHealthCheck is
+ * offered an invalid durationMs.
*/
@Test
public void testInvalidMonitoringDuration_beforeExpiry() {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), -1);
// Note: Don't move too close to the expiration time otherwise the handler will be thrashed
// by PackageWatchdog#scheduleNextSyncStateLocked which keeps posting runnables with very
// small timeouts.
@@ -1097,15 +1143,16 @@
}
/**
- * Test default monitoring duration is used when PackageWatchdog#startObservingHealth is offered
- * an invalid durationMs.
+ * Test default monitoring duration is used when PackageWatchdog#startExplicitHealthCheck is
+ * offered an invalid durationMs.
*/
@Test
public void testInvalidMonitoringDuration_afterExpiry() {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), -1);
moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS + 1);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -1127,7 +1174,8 @@
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), Long.MAX_VALUE);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), Long.MAX_VALUE);
// Raise 2 failures at t=0 and t=900 respectively
watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -1154,8 +1202,10 @@
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_B), SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_CRASH);
@@ -1174,7 +1224,8 @@
PackageWatchdog watchdog = createWatchdog();
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
VERSION_CODE)), PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
@@ -1194,7 +1245,8 @@
persistentObserver.setPersistent(true);
persistentObserver.setMayObservePackages(true);
- watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
+ watchdog.registerHealthObserver(persistentObserver, mTestExecutor);
+ watchdog.startExplicitHealthCheck(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -1212,7 +1264,8 @@
persistentObserver.setPersistent(true);
persistentObserver.setMayObservePackages(false);
- watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
+ watchdog.registerHealthObserver(persistentObserver, mTestExecutor);
+ watchdog.startExplicitHealthCheck(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -1223,13 +1276,15 @@
/** Ensure that boot loop mitigation is done when the number of boots meets the threshold. */
@Test
public void testBootLoopDetection_meetsThreshold() {
+ Slog.w("hrm1243", "I should definitely be here try 1 ");
mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
- watchdog.registerHealthObserver(bootObserver);
+ watchdog.registerHealthObserver(bootObserver, mTestExecutor);
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
assertThat(bootObserver.mitigatedBootLoop()).isTrue();
}
@@ -1237,10 +1292,11 @@
public void testBootLoopDetection_meetsThresholdRecoverability() {
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
- watchdog.registerHealthObserver(bootObserver);
+ watchdog.registerHealthObserver(bootObserver, mTestExecutor);
for (int i = 0; i < 15; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
assertThat(bootObserver.mitigatedBootLoop()).isTrue();
}
@@ -1252,10 +1308,11 @@
public void testBootLoopDetection_doesNotMeetThreshold() {
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
- watchdog.registerHealthObserver(bootObserver);
+ watchdog.registerHealthObserver(bootObserver, mTestExecutor);
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
assertThat(bootObserver.mitigatedBootLoop()).isFalse();
}
@@ -1268,10 +1325,11 @@
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
- watchdog.registerHealthObserver(bootObserver);
+ watchdog.registerHealthObserver(bootObserver, mTestExecutor);
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
assertThat(bootObserver.mitigatedBootLoop()).isFalse();
}
@@ -1286,11 +1344,12 @@
bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2);
bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
- watchdog.registerHealthObserver(bootObserver1);
- watchdog.registerHealthObserver(bootObserver2);
+ watchdog.registerHealthObserver(bootObserver1, mTestExecutor);
+ watchdog.registerHealthObserver(bootObserver2, mTestExecutor);
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
assertThat(bootObserver1.mitigatedBootLoop()).isTrue();
assertThat(bootObserver2.mitigatedBootLoop()).isFalse();
}
@@ -1302,11 +1361,12 @@
bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2);
bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
- watchdog.registerHealthObserver(bootObserver1);
- watchdog.registerHealthObserver(bootObserver2);
+ watchdog.registerHealthObserver(bootObserver1, mTestExecutor);
+ watchdog.registerHealthObserver(bootObserver2, mTestExecutor);
for (int i = 0; i < 15; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
assertThat(bootObserver1.mitigatedBootLoop()).isTrue();
assertThat(bootObserver2.mitigatedBootLoop()).isFalse();
}
@@ -1319,7 +1379,7 @@
mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
- watchdog.registerHealthObserver(bootObserver);
+ watchdog.registerHealthObserver(bootObserver, mTestExecutor);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; j++) {
watchdog.noteBoot();
@@ -1333,7 +1393,7 @@
watchdog.noteBoot();
}
}
-
+ mTestLooper.dispatchAll();
assertThat(bootObserver.mBootMitigationCounts).isEqualTo(List.of(1, 2, 3, 4, 1, 2, 3, 4));
}
@@ -1342,7 +1402,7 @@
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
- watchdog.registerHealthObserver(bootObserver);
+ watchdog.registerHealthObserver(bootObserver, mTestExecutor);
for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; j++) {
watchdog.noteBoot();
}
@@ -1358,7 +1418,7 @@
for (int i = 0; i < 4; i++) {
watchdog.noteBoot();
}
-
+ mTestLooper.dispatchAll();
assertThat(bootObserver.mBootMitigationCounts).isEqualTo(List.of(1, 2, 3, 4, 1, 2, 3, 4));
}
@@ -1370,7 +1430,8 @@
public void testNullFailedPackagesList() {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer1, List.of(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, List.of(APP_A), LONG_DURATION);
raiseFatalFailureAndDispatch(watchdog, null, PackageWatchdog.FAILURE_REASON_APP_CRASH);
assertThat(observer1.mMitigatedPackages).isEmpty();
@@ -1388,18 +1449,18 @@
PackageWatchdog watchdog = createWatchdog(testController, true);
TestObserver testObserver1 = new TestObserver(OBSERVER_NAME_1);
- watchdog.registerHealthObserver(testObserver1);
- watchdog.startObservingHealth(testObserver1, List.of(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(testObserver1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(testObserver1, List.of(APP_A), LONG_DURATION);
mTestLooper.dispatchAll();
TestObserver testObserver2 = new TestObserver(OBSERVER_NAME_2);
- watchdog.registerHealthObserver(testObserver2);
- watchdog.startObservingHealth(testObserver2, List.of(APP_B), LONG_DURATION);
+ watchdog.registerHealthObserver(testObserver2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(testObserver2, List.of(APP_B), LONG_DURATION);
mTestLooper.dispatchAll();
TestObserver testObserver3 = new TestObserver(OBSERVER_NAME_3);
- watchdog.registerHealthObserver(testObserver3);
- watchdog.startObservingHealth(testObserver3, List.of(APP_C), LONG_DURATION);
+ watchdog.registerHealthObserver(testObserver3, mTestExecutor);
+ watchdog.startExplicitHealthCheck(testObserver3, List.of(APP_C), LONG_DURATION);
mTestLooper.dispatchAll();
watchdog.unregisterHealthObserver(testObserver1);
@@ -1431,14 +1492,15 @@
public void testFailureHistoryIsPreserved() {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, List.of(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, List.of(APP_A), SHORT_DURATION);
for (int i = 0; i < PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT - 1; i++) {
watchdog.notifyPackageFailure(List.of(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
}
mTestLooper.dispatchAll();
assertThat(observer.mMitigatedPackages).isEmpty();
- watchdog.startObservingHealth(observer, List.of(APP_A), LONG_DURATION);
+ watchdog.startExplicitHealthCheck(observer, List.of(APP_A), LONG_DURATION);
watchdog.notifyPackageFailure(List.of(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
mTestLooper.dispatchAll();
@@ -1453,7 +1515,8 @@
public void testMitigationSlidingWindow() {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, List.of(APP_A),
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, List.of(APP_A),
PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS * 2);
@@ -1895,6 +1958,7 @@
}
public boolean onExecuteBootLoopMitigation(int level) {
+ Slog.w("hrm1243", "I'm here " + level);
mMitigatedBootLoop = true;
mBootMitigationCounts.add(level);
return true;