Merge "Use flags.aconfig in platform for android networking" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 654b08f..b8538fb 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -27,6 +27,7 @@
":android.os.vibrator.flags-aconfig-java{.generated_srcjars}",
":android.security.flags-aconfig-java{.generated_srcjars}",
":android.service.chooser.flags-aconfig-java{.generated_srcjars}",
+ ":android.service.dreams.flags-aconfig-java{.generated_srcjars}",
":android.service.notification.flags-aconfig-java{.generated_srcjars}",
":android.view.flags-aconfig-java{.generated_srcjars}",
":android.view.accessibility.flags-aconfig-java{.generated_srcjars}",
@@ -747,6 +748,19 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// Dreams
+aconfig_declarations {
+ name: "android.service.dreams.flags-aconfig",
+ package: "android.service.dreams",
+ srcs: ["core/java/android/service/dreams/flags.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.service.dreams.flags-aconfig-java",
+ aconfig_declarations: "android.service.dreams.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Notifications
aconfig_declarations {
name: "android.service.notification.flags-aconfig",
diff --git a/Android.bp b/Android.bp
index fa7c97d..676a0f5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -106,7 +106,7 @@
":android.hardware.radio.voice-V3-java-source",
":android.hardware.security.keymint-V3-java-source",
":android.hardware.security.secureclock-V1-java-source",
- ":android.hardware.thermal-V1-java-source",
+ ":android.hardware.thermal-V2-java-source",
":android.hardware.tv.tuner-V2-java-source",
":android.security.apc-java-source",
":android.security.authorization-java-source",
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
index ad3e422..03891bb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -314,9 +314,15 @@
if (mPackageStoppedState.contains(uid, packageName)) {
return mPackageStoppedState.get(uid, packageName);
}
- final boolean isStopped = mPackageManagerInternal.isPackageStopped(packageName, uid);
- mPackageStoppedState.add(uid, packageName, isStopped);
- return isStopped;
+
+ try {
+ final boolean isStopped = mPackageManagerInternal.isPackageStopped(packageName, uid);
+ mPackageStoppedState.add(uid, packageName, isStopped);
+ return isStopped;
+ } catch (IllegalArgumentException e) {
+ Slog.d(TAG, "Couldn't determine stopped state for unknown package: " + packageName);
+ return false;
+ }
}
@GuardedBy("mLock")
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 1c29982..8ddbf69 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -36,6 +36,7 @@
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.UidObserver;
+import android.app.job.JobInfo;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
@@ -772,18 +773,23 @@
if (!jobStatus.shouldTreatAsExpeditedJob()) {
// If quota is currently "free", then the job can run for the full amount of time,
// regardless of bucket (hence using charging instead of isQuotaFreeLocked()).
- if (mService.isBatteryCharging()
- // The top and foreground cases here were added because apps in those states
- // aren't really restricted and the work could be something the user is
- // waiting for. Now that user-initiated jobs are a defined concept, we may
- // not need these exemptions as much. However, UIJs are currently limited
- // (as of UDC) to data transfer work. There may be other work that could
- // rely on this exception. Once we add more UIJ types, we can re-evaluate
- // the need for these exceptions.
- // TODO: re-evaluate the need for these exceptions
- || mTopAppCache.get(jobStatus.getSourceUid())
+ if (mService.isBatteryCharging()) {
+ return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
+ }
+ // The top and foreground cases here were added because apps in those states
+ // aren't really restricted and the work could be something the user is
+ // waiting for. Now that user-initiated jobs are a defined concept, we may
+ // not need these exemptions as much. However, UIJs are currently limited
+ // (as of UDC) to data transfer work. There may be other work that could
+ // rely on this exception. Once we add more UIJ types, we can re-evaluate
+ // the need for these exceptions.
+ // TODO: re-evaluate the need for these exceptions
+ final boolean isInPrivilegedState = mTopAppCache.get(jobStatus.getSourceUid())
|| isTopStartedJobLocked(jobStatus)
- || isUidInForeground(jobStatus.getSourceUid())) {
+ || isUidInForeground(jobStatus.getSourceUid());
+ final boolean isJobImportant = jobStatus.getEffectivePriority() >= JobInfo.PRIORITY_HIGH
+ || (jobStatus.getFlags() & JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0;
+ if (isInPrivilegedState && isJobImportant) {
return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
}
return getTimeUntilQuotaConsumedLocked(
@@ -2549,7 +2555,25 @@
*/
@Override
public void onUsageEvent(int userId, @NonNull UsageEvents.Event event) {
- mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event).sendToTarget();
+ // Skip posting a message to the handler for events we don't care about.
+ switch (event.getEventType()) {
+ case UsageEvents.Event.ACTIVITY_RESUMED:
+ case UsageEvents.Event.ACTIVITY_PAUSED:
+ case UsageEvents.Event.ACTIVITY_STOPPED:
+ case UsageEvents.Event.ACTIVITY_DESTROYED:
+ case UsageEvents.Event.USER_INTERACTION:
+ case UsageEvents.Event.CHOOSER_ACTION:
+ case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
+ case UsageEvents.Event.NOTIFICATION_SEEN:
+ mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event)
+ .sendToTarget();
+ break;
+ default:
+ if (DEBUG) {
+ Slog.d(TAG, "Dropping event " + event.getEventType());
+ }
+ break;
+ }
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index b8397d2..357e1396 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -316,8 +316,25 @@
*/
@Override
public void onUsageEvent(int userId, @NonNull UsageEvents.Event event) {
- mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event)
- .sendToTarget();
+ // Skip posting a message to the handler for events we don't care about.
+ switch (event.getEventType()) {
+ case UsageEvents.Event.ACTIVITY_RESUMED:
+ case UsageEvents.Event.ACTIVITY_PAUSED:
+ case UsageEvents.Event.ACTIVITY_STOPPED:
+ case UsageEvents.Event.ACTIVITY_DESTROYED:
+ case UsageEvents.Event.USER_INTERACTION:
+ case UsageEvents.Event.CHOOSER_ACTION:
+ case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
+ case UsageEvents.Event.NOTIFICATION_SEEN:
+ mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event)
+ .sendToTarget();
+ break;
+ default:
+ if (DEBUG) {
+ Slog.d(TAG, "Dropping event " + event.getEventType());
+ }
+ break;
+ }
}
};
diff --git a/api/coverage/tools/ExtractFlaggedApis.kt b/api/coverage/tools/ExtractFlaggedApis.kt
index 9ffb704..43caaec 100644
--- a/api/coverage/tools/ExtractFlaggedApis.kt
+++ b/api/coverage/tools/ExtractFlaggedApis.kt
@@ -23,35 +23,43 @@
/** Usage: extract-flagged-apis <api text file> <output .pb file> */
fun main(args: Array<String>) {
var cb = ApiFile.parseApi(listOf(File(args[0])))
- val flagToApi = mutableMapOf<String, MutableList<String>>()
- cb.getPackages()
- .allClasses()
- .filter { it.methods().size > 0 }
- .forEach {
- for (method in it.methods()) {
- val flagValue =
- method.modifiers
- .findAnnotation("android.annotation.FlaggedApi")
- ?.findAttribute("value")
- ?.value
- ?.value()
- if (flagValue != null && flagValue is String) {
- val methodQualifiedName = "${it.qualifiedName()}.${method.name()}"
- if (flagToApi.containsKey(flagValue)) {
- flagToApi.get(flagValue)?.add(methodQualifiedName)
- } else {
- flagToApi.put(flagValue, mutableListOf(methodQualifiedName))
+ var builder = FlagApiMap.newBuilder()
+ for (pkg in cb.getPackages().packages) {
+ var packageName = pkg.qualifiedName()
+ pkg.allClasses()
+ .filter { it.methods().size > 0 }
+ .forEach {
+ for (method in it.methods()) {
+ val flagValue =
+ method.modifiers
+ .findAnnotation("android.annotation.FlaggedApi")
+ ?.findAttribute("value")
+ ?.value
+ ?.value()
+ if (flagValue != null && flagValue is String) {
+ var api =
+ JavaMethod.newBuilder()
+ .setPackageName(packageName)
+ .setClassName(it.fullName())
+ .setMethodName(method.name())
+ for (param in method.parameters()) {
+ api.addParameterTypes(param.type().toTypeString())
+ }
+ if (builder.containsFlagToApi(flagValue)) {
+ var updatedApis =
+ builder
+ .getFlagToApiOrThrow(flagValue)
+ .toBuilder()
+ .addJavaMethods(api)
+ .build()
+ builder.putFlagToApi(flagValue, updatedApis)
+ } else {
+ var apis = FlaggedApis.newBuilder().addJavaMethods(api).build()
+ builder.putFlagToApi(flagValue, apis)
+ }
}
}
}
- }
- var builder = FlagApiMap.newBuilder()
- for (flag in flagToApi.keys) {
- var flaggedApis = FlaggedApis.newBuilder()
- for (method in flagToApi.get(flag).orEmpty()) {
- flaggedApis.addFlaggedApi(FlaggedApi.newBuilder().setQualifiedName(method))
- }
- builder.putFlagToApi(flag, flaggedApis.build())
}
val flagApiMap = builder.build()
FileWriter(args[1]).use { it.write(flagApiMap.toString()) }
diff --git a/api/coverage/tools/extract_flagged_apis.proto b/api/coverage/tools/extract_flagged_apis.proto
index a858108..031d621 100644
--- a/api/coverage/tools/extract_flagged_apis.proto
+++ b/api/coverage/tools/extract_flagged_apis.proto
@@ -25,10 +25,13 @@
}
message FlaggedApis {
- repeated FlaggedApi flagged_api = 1;
+ repeated JavaMethod java_methods = 1;
}
-message FlaggedApi {
- string qualified_name = 1;
+message JavaMethod {
+ string package_name = 1;
+ string class_name = 2;
+ string method_name = 3;
+ repeated string parameter_types = 4;
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 61c7ec6..d490c3f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1216,6 +1216,7 @@
field public static final int opticalInsetLeft = 16844168; // 0x1010588
field public static final int opticalInsetRight = 16844170; // 0x101058a
field public static final int opticalInsetTop = 16844169; // 0x1010589
+ field @FlaggedApi("android.content.pm.sdk_lib_independence") public static final int optional;
field public static final int order = 16843242; // 0x10101ea
field public static final int orderInCategory = 16843231; // 0x10101df
field public static final int ordering = 16843490; // 0x10102e2
@@ -4467,7 +4468,7 @@
method public void onProvideKeyboardShortcuts(java.util.List<android.view.KeyboardShortcutGroup>, android.view.Menu, int);
method public android.net.Uri onProvideReferrer();
method public void onRequestPermissionsResult(int, @NonNull String[], @NonNull int[]);
- method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public void onRequestPermissionsResult(int, @NonNull String[], @NonNull int[], int);
+ method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public void onRequestPermissionsResult(int, @NonNull String[], @NonNull int[], int);
method @CallSuper protected void onRestart();
method protected void onRestoreInstanceState(@NonNull android.os.Bundle);
method public void onRestoreInstanceState(@Nullable android.os.Bundle, @Nullable android.os.PersistableBundle);
@@ -4509,7 +4510,7 @@
method public android.view.DragAndDropPermissions requestDragAndDropPermissions(android.view.DragEvent);
method public void requestFullscreenMode(int, @Nullable android.os.OutcomeReceiver<java.lang.Void,java.lang.Throwable>);
method public final void requestPermissions(@NonNull String[], int);
- method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public final void requestPermissions(@NonNull String[], int, int);
+ method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public final void requestPermissions(@NonNull String[], int, int);
method public final void requestShowKeyboardShortcuts();
method @Deprecated public boolean requestVisibleBehind(boolean);
method public final boolean requestWindowFeature(int);
@@ -4557,7 +4558,7 @@
method public void setVrModeEnabled(boolean, @NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
method public boolean shouldDockBigOverlays();
method public boolean shouldShowRequestPermissionRationale(@NonNull String);
- method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public boolean shouldShowRequestPermissionRationale(@NonNull String, int);
+ method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public boolean shouldShowRequestPermissionRationale(@NonNull String, int);
method public boolean shouldUpRecreateTask(android.content.Intent);
method public boolean showAssist(android.os.Bundle);
method @Deprecated public final void showDialog(int);
@@ -9838,7 +9839,7 @@
method public int describeContents();
method public void enforceCallingUid();
method @Nullable public String getAttributionTag();
- method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public int getDeviceId();
+ method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public int getDeviceId();
method @Nullable public android.content.AttributionSource getNext();
method @Nullable public String getPackageName();
method public int getPid();
@@ -9854,7 +9855,7 @@
ctor public AttributionSource.Builder(@NonNull android.content.AttributionSource);
method @NonNull public android.content.AttributionSource build();
method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String);
- method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @NonNull public android.content.AttributionSource.Builder setDeviceId(int);
+ method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @NonNull public android.content.AttributionSource.Builder setDeviceId(int);
method @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource);
method @FlaggedApi("android.permission.flags.set_next_attribution_source") @NonNull public android.content.AttributionSource.Builder setNextAttributionSource(@NonNull android.content.AttributionSource);
method @NonNull public android.content.AttributionSource.Builder setPackageName(@Nullable String);
@@ -36910,6 +36911,7 @@
field public static final String ACTION_REGIONAL_PREFERENCES_SETTINGS = "android.settings.REGIONAL_PREFERENCES_SETTINGS";
field public static final String ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
field public static final String ACTION_REQUEST_MANAGE_MEDIA = "android.settings.REQUEST_MANAGE_MEDIA";
+ field @FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control") public static final String ACTION_REQUEST_MEDIA_ROUTING_CONTROL = "android.settings.REQUEST_MEDIA_ROUTING_CONTROL";
field public static final String ACTION_REQUEST_SCHEDULE_EXACT_ALARM = "android.settings.REQUEST_SCHEDULE_EXACT_ALARM";
field public static final String ACTION_REQUEST_SET_AUTOFILL_SERVICE = "android.settings.REQUEST_SET_AUTOFILL_SERVICE";
field public static final String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS";
@@ -53879,9 +53881,11 @@
method @Deprecated public android.view.Display getDefaultDisplay();
method @NonNull public default android.view.WindowMetrics getMaximumWindowMetrics();
method public default boolean isCrossWindowBlurEnabled();
+ method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void registerTrustedPresentationListener(@NonNull android.os.IBinder, @NonNull android.window.TrustedPresentationThresholds, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method public default void removeCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public default void removeProposedRotationListener(@NonNull java.util.function.IntConsumer);
method public void removeViewImmediate(android.view.View);
+ method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void unregisterTrustedPresentationListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
@@ -60862,6 +60866,16 @@
method public void markSyncReady();
}
+ @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public final class TrustedPresentationThresholds implements android.os.Parcelable {
+ ctor @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public TrustedPresentationThresholds(@FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @IntRange(from=1) int);
+ method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public int describeContents();
+ method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @NonNull public static final android.os.Parcelable.Creator<android.window.TrustedPresentationThresholds> CREATOR;
+ field @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) public final float minAlpha;
+ field @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) public final float minFractionRendered;
+ field @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @IntRange(from=1) public final int stabilityRequirementMs;
+ }
+
}
package javax.microedition.khronos.egl {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index c64f230..9a65388 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3232,6 +3232,7 @@
method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void removeActivityPolicyExemption(@NonNull android.content.ComponentName);
method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDevicePolicy(int, int);
+ method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDisplayImePolicy(int, int);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void unregisterIntentInterceptor(@NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
}
@@ -4008,7 +4009,7 @@
field public static final int DELETE_FAILED_OWNER_BLOCKED = -4; // 0xfffffffc
field public static final int DELETE_KEEP_DATA = 1; // 0x1
field public static final int DELETE_SUCCEEDED = 1; // 0x1
- field @FlaggedApi("android.permission.flags.device_aware_permission_apis") public static final String EXTRA_REQUEST_PERMISSIONS_DEVICE_ID = "android.content.pm.extra.REQUEST_PERMISSIONS_DEVICE_ID";
+ field @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public static final String EXTRA_REQUEST_PERMISSIONS_DEVICE_ID = "android.content.pm.extra.REQUEST_PERMISSIONS_DEVICE_ID";
field public static final String EXTRA_REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES";
field public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES";
field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS";
@@ -4114,7 +4115,7 @@
public static interface PackageManager.OnPermissionsChangedListener {
method public void onPermissionsChanged(int);
- method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public default void onPermissionsChanged(int, @NonNull String);
+ method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public default void onPermissionsChanged(int, @NonNull String);
}
public static final class PackageManager.UninstallCompleteCallback implements android.os.Parcelable {
@@ -4540,6 +4541,7 @@
@FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final class RequestProcessor.Request {
ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public RequestProcessor.Request(@NonNull java.util.List<java.lang.Integer>, @NonNull java.util.List<android.util.Pair<android.hardware.camera2.CaptureRequest.Key,java.lang.Object>>, int);
+ method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public java.util.List<android.util.Pair<android.hardware.camera2.CaptureRequest.Key,java.lang.Object>> getParameters();
}
@FlaggedApi("com.android.internal.camera.flags.concert_mode") public static interface RequestProcessor.RequestCallback {
@@ -10925,13 +10927,13 @@
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void onGetUnusedAppCount(@NonNull java.util.function.IntConsumer);
method @BinderThread public abstract void onGrantOrUpgradeDefaultRuntimePermissions(@NonNull Runnable);
method @Deprecated @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String);
- method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String, int);
+ method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String, int);
method @Deprecated @BinderThread public void onRestoreDelayedRuntimePermissionsBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable);
method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>);
method @Deprecated @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, @NonNull Runnable);
- method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, int, @NonNull Runnable);
+ method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, int, @NonNull Runnable);
method @Deprecated @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @BinderThread public void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull android.permission.AdminPermissionControlParams, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
@@ -17177,6 +17179,9 @@
method @NonNull public default java.util.List<android.content.ComponentName> notifyScreenshotListeners(int);
method public default void registerTaskFpsCallback(@IntRange(from=0) int, @NonNull java.util.concurrent.Executor, @NonNull android.window.TaskFpsCallback);
method public default void unregisterTaskFpsCallback(@NonNull android.window.TaskFpsCallback);
+ field public static final int DISPLAY_IME_POLICY_FALLBACK_DISPLAY = 1; // 0x1
+ field public static final int DISPLAY_IME_POLICY_HIDE = 2; // 0x2
+ field public static final int DISPLAY_IME_POLICY_LOCAL = 0; // 0x0
}
public static class WindowManager.LayoutParams extends android.view.ViewGroup.LayoutParams implements android.os.Parcelable {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 3f2376d..39f2737 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -903,7 +903,7 @@
ctor public AttributionSource(int, @Nullable String, @Nullable String, @NonNull android.os.IBinder);
ctor public AttributionSource(int, @Nullable String, @Nullable String, @Nullable java.util.Set<java.lang.String>, @Nullable android.content.AttributionSource);
ctor @FlaggedApi("android.permission.flags.attribution_source_constructor") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], @Nullable android.content.AttributionSource);
- ctor @FlaggedApi("android.permission.flags.device_aware_permission_apis") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], int, @Nullable android.content.AttributionSource);
+ ctor @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], int, @Nullable android.content.AttributionSource);
method public void enforceCallingPid();
}
@@ -3619,9 +3619,6 @@
method public default void setShouldShowWithInsecureKeyguard(int, boolean);
method public default boolean shouldShowSystemDecors(int);
method @Nullable public default android.graphics.Bitmap snapshotTaskForRecents(@IntRange(from=0) int);
- field public static final int DISPLAY_IME_POLICY_FALLBACK_DISPLAY = 1; // 0x1
- field public static final int DISPLAY_IME_POLICY_HIDE = 2; // 0x2
- field public static final int DISPLAY_IME_POLICY_LOCAL = 0; // 0x0
field public static final int LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP = 600; // 0x258
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index cb08dad..ffed405 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -5572,7 +5572,7 @@
* @see #shouldShowRequestPermissionRationale
* @see Context#DEVICE_ID_DEFAULT
*/
- @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+ @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
public final void requestPermissions(@NonNull String[] permissions, int requestCode,
int deviceId) {
if (requestCode < 0) {
@@ -5645,7 +5645,7 @@
*
* @see #requestPermissions
*/
- @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+ @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults, int deviceId) {
onRequestPermissionsResult(requestCode, permissions, grantResults);
@@ -5678,7 +5678,7 @@
* @see #requestPermissions
* @see #onRequestPermissionsResult
*/
- @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+ @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
public boolean shouldShowRequestPermissionRationale(@NonNull String permission, int deviceId) {
final PackageManager packageManager = getDeviceId() == deviceId ? getPackageManager()
: createDeviceContext(deviceId).getPackageManager();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index adaaee2..8b39ed6 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1544,11 +1544,12 @@
@Override
public void dumpMemInfo(ParcelFileDescriptor pfd, Debug.MemoryInfo mem, boolean checkin,
boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly,
- boolean dumpUnreachable, String[] args) {
+ boolean dumpUnreachable, boolean dumpAllocatorStats, String[] args) {
FileOutputStream fout = new FileOutputStream(pfd.getFileDescriptor());
PrintWriter pw = new FastPrintWriter(fout);
try {
- dumpMemInfo(pw, mem, checkin, dumpFullInfo, dumpDalvik, dumpSummaryOnly, dumpUnreachable);
+ dumpMemInfo(pw, mem, checkin, dumpFullInfo, dumpDalvik, dumpSummaryOnly,
+ dumpUnreachable, dumpAllocatorStats);
} finally {
pw.flush();
IoUtils.closeQuietly(pfd);
@@ -1557,7 +1558,8 @@
@NeverCompile
private void dumpMemInfo(PrintWriter pw, Debug.MemoryInfo memInfo, boolean checkin,
- boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable) {
+ boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly,
+ boolean dumpUnreachable, boolean dumpAllocatorStats) {
long nativeMax = Debug.getNativeHeapSize() / 1024;
long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024;
long nativeFree = Debug.getNativeHeapFreeSize() / 1024;
@@ -1710,6 +1712,9 @@
pw.println(" Unreachable memory");
pw.print(Debug.getUnreachableMemory(100, showContents));
}
+ if (dumpAllocatorStats) {
+ Debug.logAllocatorStats();
+ }
}
@NeverCompile
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 75d8c10..5541e7a 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -129,7 +129,7 @@
void scheduleTrimMemory(int level);
void dumpMemInfo(in ParcelFileDescriptor fd, in Debug.MemoryInfo mem, boolean checkin,
boolean dumpInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable,
- in String[] args);
+ boolean dumpAllocatorLogs, in String[] args);
void dumpMemInfoProto(in ParcelFileDescriptor fd, in Debug.MemoryInfo mem,
boolean dumpInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable,
in String[] args);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 1e538c5..90a2659 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -16640,6 +16640,7 @@
== DEVICE_OWNER_TYPE_FINANCED;
}
+ // TODO(b/315298076): revert ag/25574027 and update the doc
/**
* Called by a device owner or profile owner of an organization-owned managed profile to enable
* or disable USB data signaling for the device. When disabled, USB data connections
@@ -16649,12 +16650,11 @@
* {@link #canUsbDataSignalingBeDisabled()} to check whether enabling or disabling USB data
* signaling is supported on the device.
*
- * Starting from {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, after the USB data signaling
+ * Starting from Android 15, after the USB data signaling
* policy has been set, {@link PolicyUpdateReceiver#onPolicySetResult(Context, String,
* Bundle, TargetUser, PolicyUpdateResult)} will notify the admin on whether the policy was
* successfully set or not. This callback will contain:
* <ul>
- * li> The policy identifier {@link DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY}
* <li> The {@link TargetUser} that this policy relates to
* <li> The {@link PolicyUpdateResult}, which will be
* {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 3520c0b..12229b1 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -219,6 +219,10 @@
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
void setShowPointerIcon(boolean showPointerIcon);
+ /** Sets an IME policy for the given display. */
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+ void setDisplayImePolicy(int displayId, int policy);
+
/**
* Registers an intent interceptor that will intercept an intent attempting to launch
* when matching the provided IntentFilter and calls the callback with the intercepted
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index 003dffb..2abeeee 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -53,6 +53,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.util.ArrayMap;
+import android.view.WindowManager;
import com.android.internal.annotations.GuardedBy;
@@ -361,6 +362,14 @@
}
}
+ void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) {
+ try {
+ mVirtualDevice.setDisplayImePolicy(displayId, policy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
void addActivityListener(
@CallbackExecutor @NonNull Executor executor,
@NonNull VirtualDeviceManager.ActivityListener listener) {
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 41c90b9..eef60f1 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -63,6 +63,7 @@
import android.os.RemoteException;
import android.util.Log;
import android.view.Surface;
+import android.view.WindowManager;
import com.android.internal.annotations.GuardedBy;
@@ -914,6 +915,24 @@
}
/**
+ * Specifies the IME behavior on the given display. By default, all displays created by
+ * virtual devices have {@link WindowManager#DISPLAY_IME_POLICY_LOCAL}.
+ *
+ * @param displayId the ID of the display to change the IME policy for. It must be owned by
+ * this virtual device.
+ * @param policy the IME policy to use on that display
+ * @throws SecurityException if the display is not owned by this device or is not
+ * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED trusted}
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ @FlaggedApi(Flags.FLAG_VDM_CUSTOM_IME)
+ public void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) {
+ if (Flags.vdmCustomIme()) {
+ mVirtualDeviceInternal.setDisplayImePolicy(displayId, policy);
+ }
+ }
+
+ /**
* Adds an activity listener to listen for events such as top activity change or virtual
* display task stack became empty.
*
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index b2074a6..a1357c9 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -173,7 +173,7 @@
/** @hide */
@TestApi
- @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+ @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
public AttributionSource(int uid, int pid, @Nullable String packageName,
@Nullable String attributionTag, @NonNull IBinder token,
@Nullable String[] renouncedPermissions,
@@ -539,7 +539,7 @@
* <p>
* This device ID is used for permissions checking during attribution source validation.
*/
- @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+ @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
public int getDeviceId() {
return mAttributionSourceState.deviceId;
}
@@ -727,7 +727,7 @@
*
* @return the builder
*/
- @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+ @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
public @NonNull Builder setDeviceId(int deviceId) {
checkNotUsed();
mBuilderFieldsSet |= 0x12;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index f865a36..e224329 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -731,7 +731,7 @@
* @see VirtualDeviceManager.VirtualDevice#getPersistentDeviceId()
* @see VirtualDeviceManager#PERSISTENT_DEVICE_ID_DEFAULT
*/
- @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+ @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
default void onPermissionsChanged(int uid, @NonNull String persistentDeviceId) {
Objects.requireNonNull(persistentDeviceId);
if (Objects.equals(persistentDeviceId,
@@ -4893,7 +4893,7 @@
* @hide
*/
@SystemApi
- @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+ @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
public static final String EXTRA_REQUEST_PERMISSIONS_DEVICE_ID =
"android.content.pm.extra.REQUEST_PERMISSIONS_DEVICE_ID";
diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java
index fdd2aa1..25ba725 100644
--- a/core/java/android/content/pm/SharedLibraryInfo.java
+++ b/core/java/android/content/pm/SharedLibraryInfo.java
@@ -142,8 +142,10 @@
mName = parcel.readString8();
mVersion = parcel.readLong();
mType = parcel.readInt();
- mDeclaringPackage = parcel.readParcelable(null, android.content.pm.VersionedPackage.class);
- mDependentPackages = parcel.readArrayList(null, android.content.pm.VersionedPackage.class);
+ mDeclaringPackage =
+ parcel.readParcelable(null, android.content.pm.VersionedPackage.class);
+ mDependentPackages =
+ parcel.readArrayList(null, android.content.pm.VersionedPackage.class);
mDependencies = parcel.createTypedArrayList(SharedLibraryInfo.CREATOR);
mIsNative = parcel.readBoolean();
}
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index c401268..57749d4 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -68,6 +68,8 @@
"authAlwaysRequiredToDisableQuietMode";
private static final String ATTR_DELETE_APP_WITH_PARENT = "deleteAppWithParent";
private static final String ATTR_ALWAYS_VISIBLE = "alwaysVisible";
+ private static final String ATTR_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING =
+ "allowStoppingUserWithDelayedLocking";
private static final String ATTR_CROSS_PROFILE_CONTENT_SHARING_STRATEGY =
"crossProfileContentSharingStrategy";
@@ -89,7 +91,8 @@
INDEX_SHOW_IN_QUIET_MODE,
INDEX_SHOW_IN_SHARING_SURFACES,
INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE,
- INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY
+ INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY,
+ INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING,
})
@Retention(RetentionPolicy.SOURCE)
private @interface PropertyIndex {
@@ -110,6 +113,7 @@
private static final int INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE = 13;
private static final int INDEX_SHOW_IN_SHARING_SURFACES = 14;
private static final int INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY = 15;
+ private static final int INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING = 16;
/** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
private long mPropertiesPresent = 0;
@@ -450,6 +454,7 @@
setCrossProfileIntentResolutionStrategy(orig.getCrossProfileIntentResolutionStrategy());
setDeleteAppWithParent(orig.getDeleteAppWithParent());
setAlwaysVisible(orig.getAlwaysVisible());
+ setAllowStoppingUserWithDelayedLocking(orig.getAllowStoppingUserWithDelayedLocking());
}
if (hasManagePermission) {
// Add items that require MANAGE_USERS or stronger.
@@ -725,6 +730,11 @@
this.mUpdateCrossProfileIntentFiltersOnOTA = val;
setPresent(INDEX_UPDATE_CROSS_PROFILE_INTENT_FILTERS_ON_OTA);
}
+ /**
+ Indicate if {@link com.android.server.pm.CrossProfileIntentFilter}s need to be updated during
+ OTA update between user-parent
+ */
+ private boolean mUpdateCrossProfileIntentFiltersOnOTA;
/**
* Returns whether a profile shares media with its parent user.
@@ -786,12 +796,38 @@
}
private boolean mAuthAlwaysRequiredToDisableQuietMode;
- /*
- Indicate if {@link com.android.server.pm.CrossProfileIntentFilter}s need to be updated during
- OTA update between user-parent
+ /**
+ * Returns whether a user (usually a profile) is allowed to leave the CE storage unlocked when
+ * stopped.
+ *
+ * <p> Setting this property to true will enable the user's CE storage to remain unlocked when
+ * the user is stopped using
+ * {@link com.android.server.am.ActivityManagerService#stopUserWithDelayedLocking(int,
+ * boolean, IStopUserCallback)}.
+ *
+ * <p> When this property is false, delayed locking may still be applicable at a global
+ * level for all users via the {@code config_multiuserDelayUserDataLocking}. That is, delayed
+ * locking for a user can happen if either the device configuration is set or if this property
+ * is set. When both, the config and the property value is false, the user storage is always
+ * locked when the user is stopped.
+ * @hide
*/
- private boolean mUpdateCrossProfileIntentFiltersOnOTA;
-
+ public boolean getAllowStoppingUserWithDelayedLocking() {
+ if (isPresent(INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING)) {
+ return mAllowStoppingUserWithDelayedLocking;
+ }
+ if (mDefaultProperties != null) {
+ return mDefaultProperties.mAllowStoppingUserWithDelayedLocking;
+ }
+ throw new SecurityException(
+ "You don't have permission to query allowStoppingUserWithDelayedLocking");
+ }
+ /** @hide */
+ public void setAllowStoppingUserWithDelayedLocking(boolean val) {
+ this.mAllowStoppingUserWithDelayedLocking = val;
+ setPresent(INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING);
+ }
+ private boolean mAllowStoppingUserWithDelayedLocking;
/**
* Returns the user's {@link CrossProfileIntentFilterAccessControlLevel}.
@@ -899,6 +935,8 @@
+ ", mCredentialShareableWithParent=" + isCredentialShareableWithParent()
+ ", mAuthAlwaysRequiredToDisableQuietMode="
+ isAuthAlwaysRequiredToDisableQuietMode()
+ + ", mAllowStoppingUserWithDelayedLocking="
+ + getAllowStoppingUserWithDelayedLocking()
+ ", mDeleteAppWithParent=" + getDeleteAppWithParent()
+ ", mAlwaysVisible=" + getAlwaysVisible()
+ ", mCrossProfileContentSharingStrategy=" + getCrossProfileContentSharingStrategy()
@@ -929,6 +967,8 @@
+ isCredentialShareableWithParent());
pw.println(prefix + " mAuthAlwaysRequiredToDisableQuietMode="
+ isAuthAlwaysRequiredToDisableQuietMode());
+ pw.println(prefix + " mAllowStoppingUserWithDelayedLocking="
+ + getAllowStoppingUserWithDelayedLocking());
pw.println(prefix + " mDeleteAppWithParent=" + getDeleteAppWithParent());
pw.println(prefix + " mAlwaysVisible=" + getAlwaysVisible());
pw.println(prefix + " mCrossProfileContentSharingStrategy="
@@ -1005,6 +1045,9 @@
case ATTR_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE:
setAuthAlwaysRequiredToDisableQuietMode(parser.getAttributeBoolean(i));
break;
+ case ATTR_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING:
+ setAllowStoppingUserWithDelayedLocking(parser.getAttributeBoolean(i));
+ break;
case ATTR_DELETE_APP_WITH_PARENT:
setDeleteAppWithParent(parser.getAttributeBoolean(i));
break;
@@ -1079,6 +1122,10 @@
serializer.attributeBoolean(null, ATTR_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE,
mAuthAlwaysRequiredToDisableQuietMode);
}
+ if (isPresent(INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING)) {
+ serializer.attributeBoolean(null, ATTR_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING,
+ mAllowStoppingUserWithDelayedLocking);
+ }
if (isPresent(INDEX_DELETE_APP_WITH_PARENT)) {
serializer.attributeBoolean(null, ATTR_DELETE_APP_WITH_PARENT,
mDeleteAppWithParent);
@@ -1110,6 +1157,7 @@
dest.writeBoolean(mMediaSharedWithParent);
dest.writeBoolean(mCredentialShareableWithParent);
dest.writeBoolean(mAuthAlwaysRequiredToDisableQuietMode);
+ dest.writeBoolean(mAllowStoppingUserWithDelayedLocking);
dest.writeBoolean(mDeleteAppWithParent);
dest.writeBoolean(mAlwaysVisible);
dest.writeInt(mCrossProfileContentSharingStrategy);
@@ -1136,6 +1184,7 @@
mMediaSharedWithParent = source.readBoolean();
mCredentialShareableWithParent = source.readBoolean();
mAuthAlwaysRequiredToDisableQuietMode = source.readBoolean();
+ mAllowStoppingUserWithDelayedLocking = source.readBoolean();
mDeleteAppWithParent = source.readBoolean();
mAlwaysVisible = source.readBoolean();
mCrossProfileContentSharingStrategy = source.readInt();
@@ -1183,6 +1232,7 @@
private boolean mMediaSharedWithParent = false;
private boolean mCredentialShareableWithParent = false;
private boolean mAuthAlwaysRequiredToDisableQuietMode = false;
+ private boolean mAllowStoppingUserWithDelayedLocking = false;
private boolean mDeleteAppWithParent = false;
private boolean mAlwaysVisible = false;
private @CrossProfileContentSharingStrategy int mCrossProfileContentSharingStrategy =
@@ -1302,6 +1352,16 @@
return this;
}
+ /** Sets the value for {@link #mAllowStoppingUserWithDelayedLocking}
+ * @hide
+ */
+ public Builder setAllowStoppingUserWithDelayedLocking(
+ boolean allowStoppingUserWithDelayedLocking) {
+ mAllowStoppingUserWithDelayedLocking =
+ allowStoppingUserWithDelayedLocking;
+ return this;
+ }
+
/** Sets the value for {@link #mDeleteAppWithParent}
* @hide
*/
@@ -1352,6 +1412,7 @@
mMediaSharedWithParent,
mCredentialShareableWithParent,
mAuthAlwaysRequiredToDisableQuietMode,
+ mAllowStoppingUserWithDelayedLocking,
mDeleteAppWithParent,
mAlwaysVisible,
mCrossProfileContentSharingStrategy);
@@ -1372,6 +1433,7 @@
boolean mediaSharedWithParent,
boolean credentialShareableWithParent,
boolean authAlwaysRequiredToDisableQuietMode,
+ boolean allowStoppingUserWithDelayedLocking,
boolean deleteAppWithParent,
boolean alwaysVisible,
@CrossProfileContentSharingStrategy int crossProfileContentSharingStrategy) {
@@ -1390,6 +1452,7 @@
setCredentialShareableWithParent(credentialShareableWithParent);
setAuthAlwaysRequiredToDisableQuietMode(
authAlwaysRequiredToDisableQuietMode);
+ setAllowStoppingUserWithDelayedLocking(allowStoppingUserWithDelayedLocking);
setDeleteAppWithParent(deleteAppWithParent);
setAlwaysVisible(alwaysVisible);
setCrossProfileContentSharingStrategy(crossProfileContentSharingStrategy);
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index e3a5520..3ab889d 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -3317,7 +3317,7 @@
* stream configurations are the same as for applications targeting SDK version older than
* 31.</p>
* <p>Refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} and
- * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations }
+ * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">the table</a>
* for additional mandatory stream configurations on a per-capability basis.</p>
* <p>*1: For JPEG format, the sizes may be restricted by below conditions:</p>
* <ul>
@@ -3436,11 +3436,12 @@
* {@link android.hardware.camera2.CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL }
* and {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES }.
* This is an app-readable conversion of the mandatory stream combination
- * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations tables}.</p>
+ * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">tables</a>.</p>
* <p>The array of
* {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
* generated according to the documented
- * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations guideline} based on specific device level and capabilities.
+ * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">guideline</a>.
+ * based on specific device level and capabilities.
* Clients can use the array as a quick reference to find an appropriate camera stream
* combination.
* As per documentation, the stream combinations with given PREVIEW, RECORD and
@@ -3469,11 +3470,12 @@
/**
* <p>An array of mandatory concurrent stream combinations.
* This is an app-readable conversion of the concurrent mandatory stream combination
- * {@link android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations tables}.</p>
+ * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#concurrent-stream-guaranteed-configurations">tables</a>.</p>
* <p>The array of
* {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
* generated according to the documented
- * {@link android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations guideline} for each device which has its Id present in the set returned by
+ * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#concurrent-stream-guaranteed-configurations">guideline</a>
+ * for each device which has its Id present in the set returned by
* {@link android.hardware.camera2.CameraManager#getConcurrentCameraIds }.
* Clients can use the array as a quick reference to find an appropriate camera stream
* combination.
@@ -3578,7 +3580,7 @@
* <p>If a camera device supports multi-resolution output streams for a particular format, for
* each of its mandatory stream combinations, the camera device will support using a
* MultiResolutionImageReader for the MAXIMUM stream of supported formats. Refer to
- * {@link android.hardware.camera2.CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs }
+ * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs">the table</a>
* for additional details.</p>
* <p>To use multi-resolution input streams, the supported formats can be queried by {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getInputFormats }.
* A reprocessable CameraCaptureSession can then be created using an {@link android.hardware.camera2.params.InputConfiguration InputConfiguration} constructed with
@@ -3587,7 +3589,7 @@
* {@code YUV} output, or multi-resolution {@code PRIVATE} input and multi-resolution
* {@code PRIVATE} output, {@code JPEG} and {@code YUV} are guaranteed to be supported
* multi-resolution output stream formats. Refer to
- * {@link android.hardware.camera2.CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs }}
+ * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs">the table</a>
* for details about the additional mandatory stream combinations in this case.</p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
*/
@@ -3700,11 +3702,12 @@
* {@link android.hardware.camera2.CaptureRequest } has {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} set
* to {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.
* This is an app-readable conversion of the maximum resolution mandatory stream combination
- * {@link android.hardware.camera2.CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors tables}.</p>
+ * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors">tables</a>.</p>
* <p>The array of
* {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
* generated according to the documented
- * {@link android.hardware.camera2.CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors guideline} for each device which has the
+ * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors">guideline</a>
+ * for each device which has the
* {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
* capability.
* Clients can use the array as a quick reference to find an appropriate camera stream
@@ -3727,11 +3730,12 @@
* 10-bit output capability
* {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT }
* This is an app-readable conversion of the 10 bit output mandatory stream combination
- * {@link android.hardware.camera2.CameraDevice#10-bit-output-additional-guaranteed-configurations tables}.</p>
+ * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#10-bit-output-additional-guaranteed-configurations">tables</a>.</p>
* <p>The array of
* {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
* generated according to the documented
- * {@link android.hardware.camera2.CameraDevice#10-bit-output-additional-guaranteed-configurations guideline} for each device which has the
+ * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#10-bit-output-additional-guaranteed-configurations">guideline</a>
+ * for each device which has the
* {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT }
* capability.
* Clients can use the array as a quick reference to find an appropriate camera stream
@@ -3752,11 +3756,12 @@
* {@code PREVIEW_STABILIZATION} in {@link CameraCharacteristics#CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES android.control.availableVideoStabilizationModes}.
* This is an app-readable conversion of the preview stabilization mandatory stream
* combination
- * {@link android.hardware.camera2.CameraDevice#preview-stabilization-guaranteed-stream-configurations tables}.</p>
+ * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#preview-stabilization-guaranteed-stream-configurations">tables</a>.</p>
* <p>The array of
* {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
* generated according to the documented
- * {@link android.hardware.camera2.CameraDevice#preview-stabilization-guaranteed-stream-configurations guideline} for each device which supports {@code PREVIEW_STABILIZATION}
+ * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#preview-stabilization-guaranteed-stream-configurations">guideline</a>
+ * for each device which supports {@code PREVIEW_STABILIZATION}
* Clients can use the array as a quick reference to find an appropriate camera stream
* combination.
* The mandatory stream combination array will be {@code null} in case the device does not
@@ -3829,8 +3834,8 @@
* <p>The guaranteed stream combinations related to stream use case for a camera device with
* {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE }
* capability is documented in the camera device
- * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations guideline}. The application is strongly recommended to use one of the guaranteed stream
- * combinations.
+ * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">guideline</a>.
+ * The application is strongly recommended to use one of the guaranteed stream combinations.
* If the application creates a session with a stream combination not in the guaranteed
* list, or with mixed DEFAULT and non-DEFAULT use cases within the same session,
* the camera device may ignore some stream use cases due to hardware constraints
@@ -3866,11 +3871,13 @@
/**
* <p>An array of mandatory stream combinations with stream use cases.
* This is an app-readable conversion of the mandatory stream combination
- * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations tables} with each stream's use case being set.</p>
+ * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">tables</a>
+ * with each stream's use case being set.</p>
* <p>The array of
* {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
* generated according to the documented
- * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations guideline} for a camera device with
+ * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">guildeline</a>
+ * for a camera device with
* {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE }
* capability.
* The mandatory stream combination array will be {@code null} in case the device doesn't
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 002c0b2..bcce4b6 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -1844,7 +1844,7 @@
* Remaps Camera Ids in the CameraService.
*
* @hide
- */
+ */
@RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA)
public void remapCameraIds(@NonNull CameraIdRemapping cameraIdRemapping)
throws CameraAccessException, SecurityException, IllegalArgumentException {
@@ -1852,6 +1852,29 @@
}
/**
+ * Injects session params into existing clients in the CameraService.
+ *
+ * @param cameraId The camera id of client to inject session params into.
+ * If no such client exists for cameraId, no injection will
+ * take place.
+ * @param sessionParams A {@link CaptureRequest} object containing the
+ * the sessionParams to inject into the existing client.
+ *
+ * @throws CameraAccessException {@link CameraAccessException#CAMERA_DISCONNECTED} will be
+ * thrown if camera service is not available. Further, if
+ * if no such client exists for cameraId,
+ * {@link CameraAccessException#CAMERA_ERROR} will be thrown.
+ * @throws SecurityException If the caller does not have permission to inject session
+ * params
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA)
+ public void injectSessionParams(@NonNull String cameraId, @NonNull CaptureRequest sessionParams)
+ throws CameraAccessException, SecurityException {
+ CameraManagerGlobal.get().injectSessionParams(cameraId, sessionParams);
+ }
+
+ /**
* Reports {@link CameraExtensionSessionStats} to the {@link ICameraService} to be logged for
* currently active session. Validation is done downstream.
*
@@ -2110,6 +2133,30 @@
}
}
+ /** Injects session params into an existing client for cameraid. */
+ public void injectSessionParams(@NonNull String cameraId,
+ @NonNull CaptureRequest sessionParams)
+ throws CameraAccessException, SecurityException {
+ synchronized (mLock) {
+ ICameraService cameraService = getCameraService();
+ if (cameraService == null) {
+ throw new CameraAccessException(
+ CameraAccessException.CAMERA_DISCONNECTED,
+ "Camera service is currently unavailable.");
+ }
+
+ try {
+ cameraService.injectSessionParams(cameraId, sessionParams.getNativeMetadata());
+ } catch (ServiceSpecificException e) {
+ throwAsPublicException(e);
+ } catch (RemoteException e) {
+ throw new CameraAccessException(
+ CameraAccessException.CAMERA_DISCONNECTED,
+ "Camera service is currently unavailable.");
+ }
+ }
+ }
+
private String[] extractCameraIdListLocked() {
String[] cameraIds = null;
int idCount = 0;
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 765a8f7..93fbe8a 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -1216,8 +1216,7 @@
* <ul>
* <li>Profile {@link android.hardware.camera2.params.DynamicRangeProfiles#HLG10 }</li>
* <li>All mandatory stream combinations for this specific capability as per
- * documentation
- * {@link android.hardware.camera2.CameraDevice#10-bit-output-additional-guaranteed-configurations }</li>
+ * <a href="CameraDevice#10-bit-output-additional-guaranteed-configurations">documentation</a></li>
* <li>In case the device is not able to capture some combination of supported
* standard 8-bit and/or 10-bit dynamic range profiles within the same capture request,
* then those constraints must be listed in
@@ -1256,8 +1255,8 @@
* </ul>
* <p>{@link android.hardware.camera2.CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES }
* lists all of the supported stream use cases.</p>
- * <p>Refer to
- * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations }
+ * <p>Refer to the
+ * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">guideline</a>
* for the mandatory stream combinations involving stream use cases, which can also be
* queried via {@link android.hardware.camera2.params.MandatoryStreamCombination }.</p>
* @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
@@ -1756,9 +1755,9 @@
/**
* <p>This camera device does not have enough capabilities to qualify as a <code>FULL</code> device or
* better.</p>
- * <p>Only the stream configurations listed in the <code>LEGACY</code> and <code>LIMITED</code> tables in the
- * {@link android.hardware.camera2.CameraDevice#limited-level-additional-guaranteed-configurations }
- * documentation are guaranteed to be supported.</p>
+ * <p>Only the stream configurations listed in the <code>LEGACY</code> and <code>LIMITED</code>
+ * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#limited-level-additional-guaranteed-configurations">tables</a>
+ * in the documentation are guaranteed to be supported.</p>
* <p>All <code>LIMITED</code> devices support the <code>BACKWARDS_COMPATIBLE</code> capability, indicating basic
* support for color image capture. The only exception is that the device may
* alternatively support only the <code>DEPTH_OUTPUT</code> capability, if it can only output depth
@@ -1784,9 +1783,9 @@
/**
* <p>This camera device is capable of supporting advanced imaging applications.</p>
- * <p>The stream configurations listed in the <code>FULL</code>, <code>LEGACY</code> and <code>LIMITED</code> tables in the
- * {@link android.hardware.camera2.CameraDevice#full-level-additional-guaranteed-configurations }
- * documentation are guaranteed to be supported.</p>
+ * <p>The stream configurations listed in the <code>FULL</code>, <code>LEGACY</code> and <code>LIMITED</code>
+ * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#full-level-additional-guaranteed-configurations">tables</a>
+ * in the documentation are guaranteed to be supported.</p>
* <p>A <code>FULL</code> device will support below capabilities:</p>
* <ul>
* <li><code>BURST_CAPTURE</code> capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
@@ -1814,9 +1813,9 @@
/**
* <p>This camera device is running in backward compatibility mode.</p>
- * <p>Only the stream configurations listed in the <code>LEGACY</code> table in the
- * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations }
- * documentation are supported.</p>
+ * <p>Only the stream configurations listed in the <code>LEGACY</code>
+ * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">table</a>
+ * in the documentation are supported.</p>
* <p>A <code>LEGACY</code> device does not support per-frame control, manual sensor control, manual
* post-processing, arbitrary cropping regions, and has relaxed performance constraints.
* No additional capabilities beyond <code>BACKWARD_COMPATIBLE</code> will ever be listed by a
@@ -1839,9 +1838,9 @@
* <p>This camera device is capable of YUV reprocessing and RAW data capture, in addition to
* FULL-level capabilities.</p>
* <p>The stream configurations listed in the <code>LEVEL_3</code>, <code>RAW</code>, <code>FULL</code>, <code>LEGACY</code> and
- * <code>LIMITED</code> tables in the
- * {@link android.hardware.camera2.CameraDevice#level-3-additional-guaranteed-configurations }
- * documentation are guaranteed to be supported.</p>
+ * <code>LIMITED</code>
+ * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#level-3-additional-guaranteed-configurations">tables</a>
+ * in the documentation are guaranteed to be supported.</p>
* <p>The following additional capabilities are guaranteed to be supported:</p>
* <ul>
* <li><code>YUV_REPROCESSING</code> capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
diff --git a/core/java/android/hardware/camera2/extension/RequestProcessor.java b/core/java/android/hardware/camera2/extension/RequestProcessor.java
index 7c099d6..bf5ea12 100644
--- a/core/java/android/hardware/camera2/extension/RequestProcessor.java
+++ b/core/java/android/hardware/camera2/extension/RequestProcessor.java
@@ -250,7 +250,7 @@
*/
@FlaggedApi(Flags.FLAG_CONCERT_MODE)
@NonNull
- List<Pair<CaptureRequest.Key, Object>> getParameters() {
+ public List<Pair<CaptureRequest.Key, Object>> getParameters() {
return mParameters;
}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 8e49c4c..134a510 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -403,14 +403,13 @@
/**
* Virtual display flag: Indicates that the display should support system decorations. Virtual
- * displays without this flag shouldn't show home, IME or any other system decorations.
+ * displays without this flag shouldn't show home, navigation bar or wallpaper.
* <p>This flag doesn't work without {@link #VIRTUAL_DISPLAY_FLAG_TRUSTED}</p>
*
* @see #createVirtualDisplay
* @see #VIRTUAL_DISPLAY_FLAG_TRUSTED
* @hide
*/
- // TODO (b/114338689): Remove the flag and use IWindowManager#setShouldShowSystemDecors
@TestApi
public static final int VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 9;
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index 7388b5b..9e09759 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -447,7 +447,8 @@
* Sets whether this display supports showing home activities and wallpaper.
*
* <p>If set to {@code true}, then the home activity relevant to this display will be
- * automatically launched upon the display creation.</p>
+ * automatically launched upon the display creation. If unset or set to {@code false}, the
+ * display will not host any activities upon creation.</p>
*
* <p>Note: setting to {@code true} requires the display to be trusted. If the display is
* not trusted, this property is ignored.</p>
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 6a83cee..05a1abea 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -33,6 +33,7 @@
*
* @hide
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public abstract class BatteryConsumer {
private static final String TAG = "BatteryConsumer";
diff --git a/core/java/android/os/CoolingDevice.java b/core/java/android/os/CoolingDevice.java
index 4ddcd9d..06ec720 100644
--- a/core/java/android/os/CoolingDevice.java
+++ b/core/java/android/os/CoolingDevice.java
@@ -55,7 +55,11 @@
TYPE_TPU,
TYPE_POWER_AMPLIFIER,
TYPE_DISPLAY,
- TYPE_SPEAKER
+ TYPE_SPEAKER,
+ TYPE_WIFI,
+ TYPE_CAMERA,
+ TYPE_FLASHLIGHT,
+ TYPE_USB_PORT
})
@Retention(RetentionPolicy.SOURCE)
public @interface Type {}
@@ -84,6 +88,14 @@
public static final int TYPE_DISPLAY = CoolingType.DISPLAY;
/** Speaker cooling device */
public static final int TYPE_SPEAKER = CoolingType.SPEAKER;
+ /** WiFi cooling device */
+ public static final int TYPE_WIFI = CoolingType.WIFI;
+ /** Camera cooling device */
+ public static final int TYPE_CAMERA = CoolingType.CAMERA;
+ /** Flashlight cooling device */
+ public static final int TYPE_FLASHLIGHT = CoolingType.FLASHLIGHT;
+ /** USB PORT cooling device */
+ public static final int TYPE_USB_PORT = CoolingType.USB_PORT;
/**
* Verify a valid cooling device type.
@@ -91,7 +103,7 @@
* @return true if a cooling device type is valid otherwise false.
*/
public static boolean isValidType(@Type int type) {
- return type >= TYPE_FAN && type <= TYPE_SPEAKER;
+ return type >= TYPE_FAN && type <= TYPE_USB_PORT;
}
public CoolingDevice(long value, @Type int type, @NonNull String name) {
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index c527cb5..04d6f61 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -2702,4 +2702,13 @@
* @hide
*/
public static native boolean isVmapStack();
+
+ /**
+ * Log internal statistics about the allocator.
+ * @return true if the statistics were logged properly, false if not.
+ *
+ * @hide
+ */
+ public static native boolean logAllocatorStats();
+
}
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index fc8523e..7e07e1f 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -1599,7 +1599,7 @@
* fully removed, otherwise system resources may leak.
* @hide
*/
- public static final native boolean sendSignalToProcessGroup(int uid, int pid, int signal);
+ public static final native int sendSignalToProcessGroup(int uid, int pid, int signal);
/**
* Freeze the cgroup for the given UID.
diff --git a/core/java/android/os/Temperature.java b/core/java/android/os/Temperature.java
index a138431..2e12278 100644
--- a/core/java/android/os/Temperature.java
+++ b/core/java/android/os/Temperature.java
@@ -79,7 +79,13 @@
TYPE_TPU,
TYPE_DISPLAY,
TYPE_MODEM,
- TYPE_SOC
+ TYPE_SOC,
+ TYPE_WIFI,
+ TYPE_CAMERA,
+ TYPE_FLASHLIGHT,
+ TYPE_SPEAKER,
+ TYPE_AMBIENT,
+ TYPE_POGO
})
@Retention(RetentionPolicy.SOURCE)
public @interface Type {}
@@ -101,6 +107,12 @@
public static final int TYPE_DISPLAY = TemperatureType.DISPLAY;
public static final int TYPE_MODEM = TemperatureType.MODEM;
public static final int TYPE_SOC = TemperatureType.SOC;
+ public static final int TYPE_WIFI = TemperatureType.WIFI;
+ public static final int TYPE_CAMERA = TemperatureType.CAMERA;
+ public static final int TYPE_FLASHLIGHT = TemperatureType.FLASHLIGHT;
+ public static final int TYPE_SPEAKER = TemperatureType.SPEAKER;
+ public static final int TYPE_AMBIENT = TemperatureType.AMBIENT;
+ public static final int TYPE_POGO = TemperatureType.POGO;
/**
* Verify a valid Temperature type.
@@ -108,7 +120,7 @@
* @return true if a Temperature type is valid otherwise false.
*/
public static boolean isValidType(@Type int type) {
- return type >= TYPE_UNKNOWN && type <= TYPE_SOC;
+ return type >= TYPE_UNKNOWN && type <= TYPE_POGO;
}
/**
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 980c13c..145981c 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -76,3 +76,11 @@
description: "Guards the ADPF GPU APIs."
bug: "284324521"
}
+
+flag {
+ name: "battery_service_support_current_adb_command"
+ namespace: "backstage_power"
+ description: "Whether or not BatteryService supports adb commands for Current values."
+ is_fixed_read_only: true
+ bug: "315037695"
+}
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 9fe2599..5a12760 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -318,7 +318,7 @@
* a virtual device. See {@link Context#DEVICE_ID_DEFAULT}
*/
@BinderThread
- @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+ @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
public void onOneTimePermissionSessionTimeout(@NonNull String packageName,
int deviceId) {
onOneTimePermissionSessionTimeout(packageName);
@@ -393,7 +393,7 @@
* @see android.content.Context#revokeSelfPermissionsOnKill(java.util.Collection)
*/
@BinderThread
- @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+ @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
public void onRevokeSelfPermissionsOnKill(@NonNull String packageName,
@NonNull List<String> permissions, int deviceId, @NonNull Runnable callback) {
onRevokeSelfPermissionsOnKill(packageName, permissions, callback);
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index cbeb821..d09c229 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -1,7 +1,7 @@
package: "android.permission.flags"
flag {
- name: "device_aware_permission_apis"
+ name: "device_aware_permission_apis_enabled"
is_fixed_read_only: true
namespace: "permissions"
description: "enable device aware permission APIs"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 8b5995a..9c27f19 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -608,6 +608,23 @@
/**
* Activity Action: Show settings to allow configuration of
+ * {@link Manifest.permission#MEDIA_ROUTING_CONTROL} permission.
+ *
+ * Input: Optionally, the Intent's data URI can specify the application package name to
+ * directly invoke the management GUI specific to the package name. For example
+ * "package:com.my.app". However, modifying this permission setting for any package is allowed
+ * only when that package holds an appropriate companion device profile such as
+ * {@link android.companion.AssociationRequest#DEVICE_PROFILE_WATCH}.
+ * <p>
+ * Output: Nothing.
+ */
+ @FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control")
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_REQUEST_MEDIA_ROUTING_CONTROL =
+ "android.settings.REQUEST_MEDIA_ROUTING_CONTROL";
+
+ /**
+ * Activity Action: Show settings to allow configuration of
* {@link Manifest.permission#RUN_USER_INITIATED_JOBS} permission
*
* Input: Optionally, the Intent's data URI can specify the application package name to
diff --git a/core/java/android/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig
new file mode 100644
index 0000000..91a713e
--- /dev/null
+++ b/core/java/android/service/dreams/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.service.dreams"
+
+flag {
+ name: "dream_overlay_host"
+ namespace: "communal"
+ description: "This flag enables using a host to handle displaying a dream's overlay rather than "
+ "relying on the dream's window"
+ bug: "291990564"
+}
diff --git a/core/java/android/service/notification/DeviceEffectsApplier.java b/core/java/android/service/notification/DeviceEffectsApplier.java
index 234ff4d..5194cdd 100644
--- a/core/java/android/service/notification/DeviceEffectsApplier.java
+++ b/core/java/android/service/notification/DeviceEffectsApplier.java
@@ -16,6 +16,8 @@
package android.service.notification;
+import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
+
/**
* Responsible for making any service calls needed to apply the set of {@link ZenDeviceEffects} that
* make sense for the current platform.
@@ -33,6 +35,13 @@
*
* <p>This will be called whenever the set of consolidated effects changes (normally through
* the activation or deactivation of zen rules).
+ *
+ * @param effects The effects that should be active and inactive.
+ * @param source The origin of the change. Because the application of specific effects can be
+ * disruptive (e.g. lead to Activity recreation), that operation can in some
+ * cases be deferred (e.g. until screen off). However, if the effects are
+ * changing as a result of an explicit user action, then it makes sense to
+ * apply them immediately regardless.
*/
- void apply(ZenDeviceEffects effects);
+ void apply(ZenDeviceEffects effects, @ConfigChangeOrigin int source);
}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index f6128ea..a5b087c 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -26,6 +26,7 @@
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AlarmManager;
@@ -61,6 +62,8 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
@@ -79,7 +82,66 @@
* @hide
*/
public class ZenModeConfig implements Parcelable {
- private static String TAG = "ZenModeConfig";
+ private static final String TAG = "ZenModeConfig";
+
+ /**
+ * The {@link ZenModeConfig} is being updated because of an unknown reason.
+ */
+ public static final int UPDATE_ORIGIN_UNKNOWN = 0;
+
+ /**
+ * The {@link ZenModeConfig} is being updated because of system initialization (i.e. load from
+ * storage, on device boot).
+ */
+ public static final int UPDATE_ORIGIN_INIT = 1;
+
+ /** The {@link ZenModeConfig} is being updated (replaced) because of a user switch or unlock. */
+ public static final int UPDATE_ORIGIN_INIT_USER = 2;
+
+ /** The {@link ZenModeConfig} is being updated because of a user action, for example:
+ * <ul>
+ * <li>{@link NotificationManager#setAutomaticZenRuleState} with a
+ * {@link Condition#source} equal to {@link Condition#SOURCE_USER_ACTION}.</li>
+ * <li>Adding, updating, or removing a rule from Settings.</li>
+ * <li>Directly activating or deactivating/snoozing a rule through some UI affordance (e.g.
+ * Quick Settings).</li>
+ * </ul>
+ */
+ public static final int UPDATE_ORIGIN_USER = 3;
+
+ /**
+ * The {@link ZenModeConfig} is being "independently" updated by an app, and not as a result of
+ * a user's action inside that app (for example, activating an {@link AutomaticZenRule} based on
+ * a previously set schedule).
+ */
+ public static final int UPDATE_ORIGIN_APP = 4;
+
+ /**
+ * The {@link ZenModeConfig} is being updated by the System or SystemUI. Note that this only
+ * includes cases where the call is coming from the System/SystemUI but the change is not due to
+ * a user action (e.g. automatically activating a schedule-based rule). If the change is a
+ * result of a user action (e.g. activating a rule by tapping on its QS tile) then
+ * {@link #UPDATE_ORIGIN_USER} is used instead.
+ */
+ public static final int UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI = 5;
+
+ /**
+ * The {@link ZenModeConfig} is being updated (replaced) because the user's DND configuration
+ * is being restored from a backup.
+ */
+ public static final int UPDATE_ORIGIN_RESTORE_BACKUP = 6;
+
+ @IntDef(prefix = { "UPDATE_ORIGIN_" }, value = {
+ UPDATE_ORIGIN_UNKNOWN,
+ UPDATE_ORIGIN_INIT,
+ UPDATE_ORIGIN_INIT_USER,
+ UPDATE_ORIGIN_USER,
+ UPDATE_ORIGIN_APP,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+ UPDATE_ORIGIN_RESTORE_BACKUP
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ConfigChangeOrigin {}
public static final int SOURCE_ANYONE = Policy.PRIORITY_SENDERS_ANY;
public static final int SOURCE_CONTACT = Policy.PRIORITY_SENDERS_CONTACTS;
diff --git a/core/java/android/service/voice/VisualQueryDetectionService.java b/core/java/android/service/voice/VisualQueryDetectionService.java
index d184b1e..76b076b 100644
--- a/core/java/android/service/voice/VisualQueryDetectionService.java
+++ b/core/java/android/service/voice/VisualQueryDetectionService.java
@@ -338,16 +338,24 @@
/**
* Overrides {@link Context#openFileInput} to read files with the given file names under the
- * internal app storage of the {@link VoiceInteractionService}, i.e., only files stored in
- * {@link Context#getFilesDir()} can be opened.
+ * internal app storage of the {@link VoiceInteractionService}, i.e., the input file path would
+ * be added with {@link Context#getFilesDir()} as prefix.
+ *
+ * @param filename Relative path of a file under {@link Context#getFilesDir()}.
+ * @throws FileNotFoundException if the file does not exist or cannot be open.
*/
@Override
- public @Nullable FileInputStream openFileInput(@NonNull String filename) throws
+ public @NonNull FileInputStream openFileInput(@NonNull String filename) throws
FileNotFoundException {
try {
AndroidFuture<ParcelFileDescriptor> future = new AndroidFuture<>();
+ assert mDetectorSessionStorageService != null;
mDetectorSessionStorageService.openFile(filename, future);
ParcelFileDescriptor pfd = future.get();
+ if (pfd == null) {
+ throw new FileNotFoundException(
+ "File does not exist. Unable to open " + filename + ".");
+ }
return new FileInputStream(pfd.getFileDescriptor());
} catch (RemoteException | ExecutionException | InterruptedException e) {
Log.w(TAG, "Cannot open file due to remote service failure");
diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java
index 91de894..b7d9705 100644
--- a/core/java/android/service/voice/VisualQueryDetector.java
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -447,12 +447,12 @@
public void onOpenFile(String filename, AndroidFuture future) throws RemoteException {
Slog.v(TAG, "BinderCallback#onOpenFile " + filename);
Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> {
- Slog.v(TAG, "onOpenFile: " + filename);
+ Slog.v(TAG, "onOpenFile: " + filename + "under internal app storage.");
File f = new File(mContext.getFilesDir(), filename);
ParcelFileDescriptor pfd = null;
try {
- Slog.d(TAG, "opened a file with ParcelFileDescriptor.");
pfd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
+ Slog.d(TAG, "Successfully opened a file with ParcelFileDescriptor.");
} catch (FileNotFoundException e) {
Slog.e(TAG, "Cannot open file. No ParcelFileDescriptor returned.");
} finally {
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index fd5517d..f28574e 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -27,9 +27,6 @@
import com.android.window.flags.Flags;
-import java.util.concurrent.Executor;
-import java.util.function.Consumer;
-
/**
* Provides an interface to the root-Surface of a View Hierarchy or Window. This
* is used in combination with the {@link android.view.SurfaceControl} API to enable
@@ -197,42 +194,6 @@
}
/**
- * Add a trusted presentation listener on the SurfaceControl associated with this window.
- *
- * @param t Transaction that the trusted presentation listener is added on. This should
- * be applied by the caller.
- * @param thresholds The {@link SurfaceControl.TrustedPresentationThresholds} that will specify
- * when the to invoke the callback.
- * @param executor The {@link Executor} where the callback will be invoked on.
- * @param listener The {@link Consumer} that will receive the callbacks when entered or
- * exited the threshold.
- *
- * @see SurfaceControl.Transaction#setTrustedPresentationCallback(SurfaceControl,
- * SurfaceControl.TrustedPresentationThresholds, Executor, Consumer)
- *
- * @hide b/287076178 un-hide with API bump
- */
- default void addTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
- @NonNull SurfaceControl.TrustedPresentationThresholds thresholds,
- @NonNull Executor executor, @NonNull Consumer<Boolean> listener) {
- }
-
- /**
- * Remove a trusted presentation listener on the SurfaceControl associated with this window.
- *
- * @param t Transaction that the trusted presentation listener removed on. This should
- * be applied by the caller.
- * @param listener The {@link Consumer} that was previously registered with
- * addTrustedPresentationCallback that should be removed.
- *
- * @see SurfaceControl.Transaction#clearTrustedPresentationCallback(SurfaceControl)
- * @hide b/287076178 un-hide with API bump
- */
- default void removeTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
- @NonNull Consumer<Boolean> listener) {
- }
-
- /**
* Transfer the currently in progress touch gesture from the host to the requested
* {@link SurfaceControlViewHost.SurfacePackage}. This requires that the
* SurfaceControlViewHost was created with the current host's inputToken.
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 07dd882..1908c64c 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -276,15 +276,14 @@
/**
* Display flag: Indicates that the display should show system decorations.
* <p>
- * This flag identifies secondary displays that should show system decorations, such as status
- * bar, navigation bar, home activity or IME.
+ * This flag identifies secondary displays that should show system decorations, such as
+ * navigation bar, home activity or wallpaper.
* </p>
* <p>Note that this flag doesn't work without {@link #FLAG_TRUSTED}</p>
*
* @see #getFlags()
* @hide
*/
- // TODO (b/114338689): Remove the flag and use IWindowManager#setShouldShowSystemDecors
public static final int FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 6;
/**
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 17bbee6d0..36b74e3 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -73,6 +73,8 @@
import android.window.ITaskFpsCallback;
import android.window.ScreenCapture;
import android.window.WindowContextInfo;
+import android.window.ITrustedPresentationListener;
+import android.window.TrustedPresentationThresholds;
/**
* System private interface to the window manager.
@@ -1075,4 +1077,10 @@
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ ".permission.MONITOR_INPUT)")
void unregisterDecorViewGestureListener(IDecorViewGestureListener listener, int displayId);
+
+ void registerTrustedPresentationListener(in IBinder window, in ITrustedPresentationListener listener,
+ in TrustedPresentationThresholds thresholds, int id);
+
+
+ void unregisterTrustedPresentationListener(in ITrustedPresentationListener listener, int id);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 7134529..5cbb42e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -826,11 +826,19 @@
* The resolved pointer icon type requested by this window.
* A null value indicates the resolved pointer icon has not yet been calculated.
*/
+ // TODO(b/293587049): Remove pointer icon tracking by type when refactor is complete.
@Nullable
private Integer mPointerIconType = null;
private PointerIcon mCustomPointerIcon = null;
/**
+ * The resolved pointer icon requested by this window.
+ * A null value indicates the resolved pointer icon has not yet been calculated.
+ */
+ @Nullable
+ private PointerIcon mResolvedPointerIcon = null;
+
+ /**
* see {@link #playSoundEffect(int)}
*/
AudioManager mAudioManager;
@@ -7410,12 +7418,14 @@
// Other apps or the window manager may change the icon type outside of
// this app, therefore the icon type has to be reset on enter/exit event.
mPointerIconType = null;
+ mResolvedPointerIcon = null;
}
if (action != MotionEvent.ACTION_HOVER_EXIT) {
// Resolve the pointer icon
if (!updatePointerIcon(event) && action == MotionEvent.ACTION_HOVER_MOVE) {
mPointerIconType = null;
+ mResolvedPointerIcon = null;
}
}
@@ -7470,6 +7480,7 @@
private void resetPointerIcon(MotionEvent event) {
mPointerIconType = null;
+ mResolvedPointerIcon = null;
updatePointerIcon(event);
}
@@ -7507,6 +7518,21 @@
pointerIcon = mView.onResolvePointerIcon(event, pointerIndex);
}
+ if (enablePointerChoreographer()) {
+ if (pointerIcon == null) {
+ pointerIcon = PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED);
+ }
+ if (Objects.equals(mResolvedPointerIcon, pointerIcon)) {
+ return true;
+ }
+ mResolvedPointerIcon = pointerIcon;
+
+ InputManagerGlobal.getInstance()
+ .setPointerIcon(pointerIcon, event.getDisplayId(),
+ event.getDeviceId(), event.getPointerId(0), getInputToken());
+ return true;
+ }
+
final int pointerType = (pointerIcon != null) ?
pointerIcon.getType() : PointerIcon.TYPE_NOT_SPECIFIED;
@@ -7514,34 +7540,18 @@
mPointerIconType = pointerType;
mCustomPointerIcon = null;
if (mPointerIconType != PointerIcon.TYPE_CUSTOM) {
- if (enablePointerChoreographer()) {
- InputManagerGlobal
- .getInstance()
- .setPointerIcon(PointerIcon.getSystemIcon(mContext, pointerType),
- event.getDisplayId(), event.getDeviceId(),
- event.getPointerId(pointerIndex), getInputToken());
- } else {
- InputManagerGlobal
- .getInstance()
- .setPointerIconType(pointerType);
- }
+ InputManagerGlobal
+ .getInstance()
+ .setPointerIconType(pointerType);
return true;
}
}
if (mPointerIconType == PointerIcon.TYPE_CUSTOM &&
!pointerIcon.equals(mCustomPointerIcon)) {
mCustomPointerIcon = pointerIcon;
- if (enablePointerChoreographer()) {
- InputManagerGlobal
- .getInstance()
- .setPointerIcon(mCustomPointerIcon,
- event.getDisplayId(), event.getDeviceId(),
- event.getPointerId(pointerIndex), getInputToken());
- } else {
- InputManagerGlobal
- .getInstance()
- .setCustomPointerIcon(mCustomPointerIcon);
- }
+ InputManagerGlobal
+ .getInstance()
+ .setCustomPointerIcon(mCustomPointerIcon);
}
return true;
}
@@ -12016,18 +12026,6 @@
scheduleTraversals();
}
- @Override
- public void addTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
- @NonNull SurfaceControl.TrustedPresentationThresholds thresholds,
- @NonNull Executor executor, @NonNull Consumer<Boolean> listener) {
- t.setTrustedPresentationCallback(getSurfaceControl(), thresholds, executor, listener);
- }
-
- @Override
- public void removeTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
- @NonNull Consumer<Boolean> listener) {
- t.clearTrustedPresentationCallback(getSurfaceControl());
- }
private void logAndTrace(String msg) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 046ea77..c7e1807 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -122,7 +122,11 @@
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.window.ITrustedPresentationListener;
import android.window.TaskFpsCallback;
+import android.window.TrustedPresentationThresholds;
+
+import com.android.window.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -656,26 +660,35 @@
/**
* Display IME Policy: The IME should appear on the local display.
+ *
* @hide
*/
- @TestApi
+ @SuppressLint("UnflaggedApi") // promoting from @TestApi.
+ @SystemApi
int DISPLAY_IME_POLICY_LOCAL = 0;
/**
- * Display IME Policy: The IME should appear on the fallback display.
+ * Display IME Policy: The IME should appear on a fallback display.
+ *
+ * <p>The fallback display is always {@link Display#DEFAULT_DISPLAY}.</p>
+ *
* @hide
*/
- @TestApi
+ @SuppressLint("UnflaggedApi") // promoting from @TestApi.
+ @SystemApi
int DISPLAY_IME_POLICY_FALLBACK_DISPLAY = 1;
/**
* Display IME Policy: The IME should be hidden.
*
- * Setting this policy will prevent the IME from making a connection. This
- * will prevent any IME from receiving metadata about input.
+ * <p>Setting this policy will prevent the IME from making a connection. This
+ * will prevent any IME from receiving metadata about input and this display will effectively
+ * have no IME.</p>
+ *
* @hide
*/
- @TestApi
+ @SuppressLint("UnflaggedApi") // promoting from @TestApi.
+ @SystemApi
int DISPLAY_IME_POLICY_HIDE = 2;
/**
@@ -3251,6 +3264,13 @@
public static final int PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC = 1 << 24;
/**
+ * Flag to indicate that the window consumes the insets of {@link Type#ime()}. This makes
+ * windows below this window unable to receive visible IME insets.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_CONSUME_IME_INSETS = 1 << 25;
+
+ /**
* Flag to indicate that the window is controlling the appearance of system bars. So we
* don't need to adjust it by reading its system UI flags for compatibility.
* @hide
@@ -3334,6 +3354,7 @@
PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION,
PRIVATE_FLAG_NOT_MAGNIFIABLE,
PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
+ PRIVATE_FLAG_CONSUME_IME_INSETS,
PRIVATE_FLAG_APPEARANCE_CONTROLLED,
PRIVATE_FLAG_BEHAVIOR_CONTROLLED,
PRIVATE_FLAG_FIT_INSETS_CONTROLLED,
@@ -3432,6 +3453,10 @@
equals = PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
name = "COLOR_SPACE_AGNOSTIC"),
@ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_CONSUME_IME_INSETS,
+ equals = PRIVATE_FLAG_CONSUME_IME_INSETS,
+ name = "CONSUME_IME_INSETS"),
+ @ViewDebug.FlagToString(
mask = PRIVATE_FLAG_APPEARANCE_CONTROLLED,
equals = PRIVATE_FLAG_APPEARANCE_CONTROLLED,
name = "APPEARANCE_CONTROLLED"),
@@ -3458,7 +3483,7 @@
@ViewDebug.FlagToString(
mask = PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY,
equals = PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY,
- name = "PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY")
+ name = "SYSTEM_APPLICATION_OVERLAY")
})
@PrivateFlags
@TestApi
@@ -5884,4 +5909,34 @@
default boolean replaceContentOnDisplayWithSc(int displayId, @NonNull SurfaceControl sc) {
throw new UnsupportedOperationException();
}
+
+ /**
+ * Add a trusted presentation listener associated with a window.
+ *
+ * <p> If this listener is already registered then the window and thresholds will be updated.
+ *
+ * @param window The Window to add the trusted presentation listener for
+ * @param thresholds The {@link TrustedPresentationThresholds} that will specify
+ * when the to invoke the callback.
+ * @param executor The {@link Executor} where the callback will be invoked on.
+ * @param listener The {@link Consumer} that will receive the callbacks
+ * when entered or exited trusted presentation per the thresholds.
+ */
+ @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+ default void registerTrustedPresentationListener(@NonNull IBinder window,
+ @NonNull TrustedPresentationThresholds thresholds, @NonNull Executor executor,
+ @NonNull Consumer<Boolean> listener) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Removes a presentation listener associated with a window. If the listener was not previously
+ * registered, the call will be a noop.
+ *
+ * @see WindowManager#registerTrustedPresentationListener(IBinder, TrustedPresentationThresholds, Executor, Consumer)
+ */
+ @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+ default void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 214f1ec..f1e4061 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -30,9 +30,13 @@
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.util.AndroidRuntimeException;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Pair;
import android.view.inputmethod.InputMethodManager;
+import android.window.ITrustedPresentationListener;
+import android.window.TrustedPresentationThresholds;
import com.android.internal.util.FastPrintWriter;
@@ -43,6 +47,7 @@
import java.util.ArrayList;
import java.util.WeakHashMap;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
import java.util.function.IntConsumer;
/**
@@ -143,6 +148,9 @@
private Runnable mSystemPropertyUpdater;
+ private final TrustedPresentationListener mTrustedPresentationListener =
+ new TrustedPresentationListener();
+
private WindowManagerGlobal() {
}
@@ -324,7 +332,7 @@
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
- & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
+ & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
@@ -482,7 +490,7 @@
if (who != null) {
WindowLeaked leak = new WindowLeaked(
what + " " + who + " has leaked window "
- + root.getView() + " that was originally added here");
+ + root.getView() + " that was originally added here");
leak.setStackTrace(root.getLocation().getStackTrace());
Log.e(TAG, "", leak);
}
@@ -790,6 +798,87 @@
}
}
+ public void registerTrustedPresentationListener(@NonNull IBinder window,
+ @NonNull TrustedPresentationThresholds thresholds, Executor executor,
+ @NonNull Consumer<Boolean> listener) {
+ mTrustedPresentationListener.addListener(window, thresholds, listener, executor);
+ }
+
+ public void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) {
+ mTrustedPresentationListener.removeListener(listener);
+ }
+
+ private final class TrustedPresentationListener extends
+ ITrustedPresentationListener.Stub {
+ private static int sId = 0;
+ private final ArrayMap<Consumer<Boolean>, Pair<Integer, Executor>> mListeners =
+ new ArrayMap<>();
+
+ private final Object mTplLock = new Object();
+
+ private void addListener(IBinder window, TrustedPresentationThresholds thresholds,
+ Consumer<Boolean> listener, Executor executor) {
+ synchronized (mTplLock) {
+ if (mListeners.containsKey(listener)) {
+ Log.i(TAG, "Updating listener " + listener + " thresholds to " + thresholds);
+ removeListener(listener);
+ }
+ int id = sId++;
+ mListeners.put(listener, new Pair<>(id, executor));
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .registerTrustedPresentationListener(window, this, thresholds, id);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+ }
+
+ private void removeListener(Consumer<Boolean> listener) {
+ synchronized (mTplLock) {
+ var removedListener = mListeners.remove(listener);
+ if (removedListener == null) {
+ Log.i(TAG, "listener " + listener + " does not exist.");
+ return;
+ }
+
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .unregisterTrustedPresentationListener(this, removedListener.first);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+ }
+
+ @Override
+ public void onTrustedPresentationChanged(int[] inTrustedStateListenerIds,
+ int[] outOfTrustedStateListenerIds) {
+ ArrayList<Runnable> firedListeners = new ArrayList<>();
+ synchronized (mTplLock) {
+ mListeners.forEach((listener, idExecutorPair) -> {
+ final var listenerId = idExecutorPair.first;
+ final var executor = idExecutorPair.second;
+ for (int id : inTrustedStateListenerIds) {
+ if (listenerId == id) {
+ firedListeners.add(() -> executor.execute(
+ () -> listener.accept(/*presentationState*/true)));
+ }
+ }
+ for (int id : outOfTrustedStateListenerIds) {
+ if (listenerId == id) {
+ firedListeners.add(() -> executor.execute(
+ () -> listener.accept(/*presentationState*/false)));
+ }
+ }
+ });
+ }
+ for (int i = 0; i < firedListeners.size(); i++) {
+ firedListeners.get(i).run();
+ }
+ }
+ }
+
/** @hide */
public void addWindowlessRoot(ViewRootImpl impl) {
synchronized (mLock) {
@@ -801,7 +890,7 @@
public void removeWindowlessRoot(ViewRootImpl impl) {
synchronized (mLock) {
mWindowlessRoots.remove(impl);
- }
+ }
}
public void setRecentsAppBehindSystemBars(boolean behindSystemBars) {
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index d7b74b3..b4b1fde 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -37,6 +37,7 @@
import android.util.Log;
import android.window.ITaskFpsCallback;
import android.window.TaskFpsCallback;
+import android.window.TrustedPresentationThresholds;
import android.window.WindowContext;
import android.window.WindowMetricsController;
import android.window.WindowProvider;
@@ -508,4 +509,17 @@
}
return false;
}
+
+ @Override
+ public void registerTrustedPresentationListener(@NonNull IBinder window,
+ @NonNull TrustedPresentationThresholds thresholds, @NonNull Executor executor,
+ @NonNull Consumer<Boolean> listener) {
+ mGlobal.registerTrustedPresentationListener(window, thresholds, executor, listener);
+ }
+
+ @Override
+ public void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) {
+ mGlobal.unregisterTrustedPresentationListener(listener);
+
+ }
}
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index fab8c77..1b7d57b 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -20,8 +20,8 @@
import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
import static com.android.internal.inputmethod.InputMethodDebug.softInputDisplayReasonToString;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_HIDE_ANIMATION;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_SHOW_ANIMATION;
+import static com.android.internal.jank.Cuj.CUJ_IME_INSETS_HIDE_ANIMATION;
+import static com.android.internal.jank.Cuj.CUJ_IME_INSETS_SHOW_ANIMATION;
import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_HIDDEN;
import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_SHOWN;
@@ -758,7 +758,7 @@
* A helper method to translate animation type to CUJ type for IME animations.
*
* @param animType the animation type.
- * @return the integer in {@link com.android.internal.jank.InteractionJankMonitor.CujType},
+ * @return the integer in {@link com.android.internal.jank.Cuj.CujType},
* or {@code -1} if the animation type is not supported for tracking yet.
*/
private static int getImeInsetsCujFromAnimation(@AnimationType int animType) {
diff --git a/core/java/android/window/ITrustedPresentationListener.aidl b/core/java/android/window/ITrustedPresentationListener.aidl
new file mode 100644
index 0000000..b33128a
--- /dev/null
+++ b/core/java/android/window/ITrustedPresentationListener.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+/**
+ * @hide
+ */
+oneway interface ITrustedPresentationListener {
+ void onTrustedPresentationChanged(in int[] enteredTrustedStateIds, in int[] exitedTrustedStateIds);
+}
\ No newline at end of file
diff --git a/core/java/android/window/TrustedPresentationListener.java b/core/java/android/window/TrustedPresentationListener.java
new file mode 100644
index 0000000..02fd6d9
--- /dev/null
+++ b/core/java/android/window/TrustedPresentationListener.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+/**
+ * @hide
+ */
+public interface TrustedPresentationListener {
+
+ void onTrustedPresentationChanged(boolean inTrustedPresentationState);
+
+}
diff --git a/core/java/android/window/TrustedPresentationThresholds.aidl b/core/java/android/window/TrustedPresentationThresholds.aidl
new file mode 100644
index 0000000..d7088bf
--- /dev/null
+++ b/core/java/android/window/TrustedPresentationThresholds.aidl
@@ -0,0 +1,3 @@
+package android.window;
+
+parcelable TrustedPresentationThresholds;
diff --git a/core/java/android/window/TrustedPresentationThresholds.java b/core/java/android/window/TrustedPresentationThresholds.java
new file mode 100644
index 0000000..90f8834
--- /dev/null
+++ b/core/java/android/window/TrustedPresentationThresholds.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.annotation.FlaggedApi;
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.SuppressLint;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.SurfaceControl;
+
+import androidx.annotation.NonNull;
+
+import com.android.window.flags.Flags;
+
+/**
+ * Threshold values that are sent with
+ * {@link android.view.WindowManager#registerTrustedPresentationListener(IBinder,
+ * TrustedPresentationThresholds, Executor, Consumer)}
+ */
+@FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+public final class TrustedPresentationThresholds implements Parcelable {
+ /**
+ * The min alpha the {@link SurfaceControl} is required to have to be considered inside the
+ * threshold.
+ */
+ @FloatRange(from = 0f, fromInclusive = false, to = 1f)
+ @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+ @SuppressLint("InternalField") // simple data class
+ public final float minAlpha;
+
+ /**
+ * The min fraction of the SurfaceControl that was presented to the user to be considered
+ * inside the threshold.
+ */
+ @FloatRange(from = 0f, fromInclusive = false, to = 1f)
+ @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+ @SuppressLint("InternalField") // simple data class
+ public final float minFractionRendered;
+
+ /**
+ * The time in milliseconds required for the {@link SurfaceControl} to be in the threshold.
+ */
+ @IntRange(from = 1)
+ @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+ @SuppressLint("InternalField") // simple data class
+ public final int stabilityRequirementMs;
+
+ private void checkValid() {
+ if (minAlpha <= 0 || minFractionRendered <= 0 || stabilityRequirementMs < 1) {
+ throw new IllegalArgumentException(
+ "TrustedPresentationThresholds values are invalid");
+ }
+ }
+
+ /**
+ * Creates a new TrustedPresentationThresholds.
+ *
+ * @param minAlpha The min alpha the {@link SurfaceControl} is required to
+ * have to be considered inside the
+ * threshold.
+ * @param minFractionRendered The min fraction of the SurfaceControl that was presented
+ * to the user to be considered
+ * inside the threshold.
+ * @param stabilityRequirementMs The time in milliseconds required for the
+ * {@link SurfaceControl} to be in the threshold.
+ */
+ @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+ public TrustedPresentationThresholds(
+ @FloatRange(from = 0f, fromInclusive = false, to = 1f) float minAlpha,
+ @FloatRange(from = 0f, fromInclusive = false, to = 1f) float minFractionRendered,
+ @IntRange(from = 1) int stabilityRequirementMs) {
+ this.minAlpha = minAlpha;
+ this.minFractionRendered = minFractionRendered;
+ this.stabilityRequirementMs = stabilityRequirementMs;
+ checkValid();
+ }
+
+ @Override
+ @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+ public String toString() {
+ return "TrustedPresentationThresholds { "
+ + "minAlpha = " + minAlpha + ", "
+ + "minFractionRendered = " + minFractionRendered + ", "
+ + "stabilityRequirementMs = " + stabilityRequirementMs
+ + " }";
+ }
+
+ @Override
+ @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeFloat(minAlpha);
+ dest.writeFloat(minFractionRendered);
+ dest.writeInt(stabilityRequirementMs);
+ }
+
+ @Override
+ @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * @hide
+ */
+ TrustedPresentationThresholds(@NonNull Parcel in) {
+ minAlpha = in.readFloat();
+ minFractionRendered = in.readFloat();
+ stabilityRequirementMs = in.readInt();
+
+ checkValid();
+ }
+
+ @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+ public static final @NonNull Creator<TrustedPresentationThresholds> CREATOR =
+ new Creator<TrustedPresentationThresholds>() {
+ @Override
+ @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+ public TrustedPresentationThresholds[] newArray(int size) {
+ return new TrustedPresentationThresholds[size];
+ }
+
+ @Override
+ @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+ public TrustedPresentationThresholds createFromParcel(@NonNull Parcel in) {
+ return new TrustedPresentationThresholds(in);
+ }
+ };
+}
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index 29932f3..56df493 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -56,3 +56,11 @@
is_fixed_read_only: true
bug: "308662081"
}
+
+flag {
+ namespace: "window_surfaces"
+ name: "trusted_presentation_listener_for_window"
+ description: "Enable trustedPresentationListener on windows public API"
+ is_fixed_read_only: true
+ bug: "278027319"
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
new file mode 100644
index 0000000..f460233
--- /dev/null
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -0,0 +1,503 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.jank;
+
+import android.annotation.IntDef;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+
+/** @hide */
+public class Cuj {
+ @VisibleForTesting
+ public static final int MAX_LENGTH_OF_CUJ_NAME = 80;
+
+ // Every value must have a corresponding entry in CUJ_STATSD_INTERACTION_TYPE.
+ public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = 0;
+ public static final int CUJ_NOTIFICATION_SHADE_SCROLL_FLING = 2;
+ public static final int CUJ_NOTIFICATION_SHADE_ROW_EXPAND = 3;
+ public static final int CUJ_NOTIFICATION_SHADE_ROW_SWIPE = 4;
+ public static final int CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE = 5;
+ public static final int CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE = 6;
+ public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS = 7;
+ public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON = 8;
+ public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME = 9;
+ public static final int CUJ_LAUNCHER_APP_CLOSE_TO_PIP = 10;
+ public static final int CUJ_LAUNCHER_QUICK_SWITCH = 11;
+ public static final int CUJ_NOTIFICATION_HEADS_UP_APPEAR = 12;
+ public static final int CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR = 13;
+ public static final int CUJ_NOTIFICATION_ADD = 14;
+ public static final int CUJ_NOTIFICATION_REMOVE = 15;
+ public static final int CUJ_NOTIFICATION_APP_START = 16;
+ public static final int CUJ_LOCKSCREEN_PASSWORD_APPEAR = 17;
+ public static final int CUJ_LOCKSCREEN_PATTERN_APPEAR = 18;
+ public static final int CUJ_LOCKSCREEN_PIN_APPEAR = 19;
+ public static final int CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR = 20;
+ public static final int CUJ_LOCKSCREEN_PATTERN_DISAPPEAR = 21;
+ public static final int CUJ_LOCKSCREEN_PIN_DISAPPEAR = 22;
+ public static final int CUJ_LOCKSCREEN_TRANSITION_FROM_AOD = 23;
+ public static final int CUJ_LOCKSCREEN_TRANSITION_TO_AOD = 24;
+ public static final int CUJ_LAUNCHER_OPEN_ALL_APPS = 25;
+ public static final int CUJ_LAUNCHER_ALL_APPS_SCROLL = 26;
+ public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET = 27;
+ public static final int CUJ_SETTINGS_PAGE_SCROLL = 28;
+ public static final int CUJ_LOCKSCREEN_UNLOCK_ANIMATION = 29;
+ public static final int CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON = 30;
+ public static final int CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER = 31;
+ public static final int CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE = 32;
+ public static final int CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON = 33;
+ public static final int CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP = 34;
+ public static final int CUJ_PIP_TRANSITION = 35;
+ public static final int CUJ_WALLPAPER_TRANSITION = 36;
+ public static final int CUJ_USER_SWITCH = 37;
+ public static final int CUJ_SPLASHSCREEN_AVD = 38;
+ public static final int CUJ_SPLASHSCREEN_EXIT_ANIM = 39;
+ public static final int CUJ_SCREEN_OFF = 40;
+ public static final int CUJ_SCREEN_OFF_SHOW_AOD = 41;
+ public static final int CUJ_ONE_HANDED_ENTER_TRANSITION = 42;
+ public static final int CUJ_ONE_HANDED_EXIT_TRANSITION = 43;
+ public static final int CUJ_UNFOLD_ANIM = 44;
+ public static final int CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS = 45;
+ public static final int CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS = 46;
+ public static final int CUJ_SUW_LOADING_TO_NEXT_FLOW = 47;
+ public static final int CUJ_SUW_LOADING_SCREEN_FOR_STATUS = 48;
+ public static final int CUJ_SPLIT_SCREEN_ENTER = 49;
+ public static final int CUJ_SPLIT_SCREEN_EXIT = 50;
+ public static final int CUJ_LOCKSCREEN_LAUNCH_CAMERA = 51; // reserved.
+ public static final int CUJ_SPLIT_SCREEN_RESIZE = 52;
+ public static final int CUJ_SETTINGS_SLIDER = 53;
+ public static final int CUJ_TAKE_SCREENSHOT = 54;
+ public static final int CUJ_VOLUME_CONTROL = 55;
+ public static final int CUJ_BIOMETRIC_PROMPT_TRANSITION = 56;
+ public static final int CUJ_SETTINGS_TOGGLE = 57;
+ public static final int CUJ_SHADE_DIALOG_OPEN = 58;
+ public static final int CUJ_USER_DIALOG_OPEN = 59;
+ public static final int CUJ_TASKBAR_EXPAND = 60;
+ public static final int CUJ_TASKBAR_COLLAPSE = 61;
+ public static final int CUJ_SHADE_CLEAR_ALL = 62;
+ public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION = 63;
+ public static final int CUJ_LOCKSCREEN_OCCLUSION = 64;
+ public static final int CUJ_RECENTS_SCROLLING = 65;
+ public static final int CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS = 66;
+ public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE = 67;
+ public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME = 68;
+ public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = 70;
+ public static final int CUJ_LAUNCHER_OPEN_SEARCH_RESULT = 71;
+ // 72 - 77 are reserved for b/281564325.
+
+ /**
+ * In some cases when we do not have any end-target, we play a simple slide-down animation.
+ * eg: Open an app from Overview/Task switcher such that there is no home-screen icon.
+ * eg: Exit the app using back gesture.
+ */
+ public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK = 78;
+ public static final int CUJ_IME_INSETS_SHOW_ANIMATION = 80;
+ public static final int CUJ_IME_INSETS_HIDE_ANIMATION = 81;
+
+ public static final int CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER = 82;
+
+ public static final int CUJ_LAUNCHER_UNFOLD_ANIM = 83;
+
+ public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = 84;
+ public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = 85;
+ public static final int CUJ_PREDICTIVE_BACK_HOME = 86;
+
+ // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
+ @VisibleForTesting
+ static final int LAST_CUJ = CUJ_PREDICTIVE_BACK_HOME;
+
+ /** @hide */
+ @IntDef({
+ CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
+ CUJ_NOTIFICATION_SHADE_SCROLL_FLING,
+ CUJ_NOTIFICATION_SHADE_ROW_EXPAND,
+ CUJ_NOTIFICATION_SHADE_ROW_SWIPE,
+ CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
+ CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE,
+ CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS,
+ CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON,
+ CUJ_LAUNCHER_APP_CLOSE_TO_HOME,
+ CUJ_LAUNCHER_APP_CLOSE_TO_PIP,
+ CUJ_LAUNCHER_QUICK_SWITCH,
+ CUJ_NOTIFICATION_HEADS_UP_APPEAR,
+ CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR,
+ CUJ_NOTIFICATION_ADD,
+ CUJ_NOTIFICATION_REMOVE,
+ CUJ_NOTIFICATION_APP_START,
+ CUJ_LOCKSCREEN_PASSWORD_APPEAR,
+ CUJ_LOCKSCREEN_PATTERN_APPEAR,
+ CUJ_LOCKSCREEN_PIN_APPEAR,
+ CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR,
+ CUJ_LOCKSCREEN_PATTERN_DISAPPEAR,
+ CUJ_LOCKSCREEN_PIN_DISAPPEAR,
+ CUJ_LOCKSCREEN_TRANSITION_FROM_AOD,
+ CUJ_LOCKSCREEN_TRANSITION_TO_AOD,
+ CUJ_LAUNCHER_OPEN_ALL_APPS,
+ CUJ_LAUNCHER_ALL_APPS_SCROLL,
+ CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET,
+ CUJ_SETTINGS_PAGE_SCROLL,
+ CUJ_LOCKSCREEN_UNLOCK_ANIMATION,
+ CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON,
+ CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER,
+ CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
+ CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON,
+ CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
+ CUJ_PIP_TRANSITION,
+ CUJ_WALLPAPER_TRANSITION,
+ CUJ_USER_SWITCH,
+ CUJ_SPLASHSCREEN_AVD,
+ CUJ_SPLASHSCREEN_EXIT_ANIM,
+ CUJ_SCREEN_OFF,
+ CUJ_SCREEN_OFF_SHOW_AOD,
+ CUJ_ONE_HANDED_ENTER_TRANSITION,
+ CUJ_ONE_HANDED_EXIT_TRANSITION,
+ CUJ_UNFOLD_ANIM,
+ CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS,
+ CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS,
+ CUJ_SUW_LOADING_TO_NEXT_FLOW,
+ CUJ_SUW_LOADING_SCREEN_FOR_STATUS,
+ CUJ_SPLIT_SCREEN_ENTER,
+ CUJ_SPLIT_SCREEN_EXIT,
+ CUJ_LOCKSCREEN_LAUNCH_CAMERA,
+ CUJ_SPLIT_SCREEN_RESIZE,
+ CUJ_SETTINGS_SLIDER,
+ CUJ_TAKE_SCREENSHOT,
+ CUJ_VOLUME_CONTROL,
+ CUJ_BIOMETRIC_PROMPT_TRANSITION,
+ CUJ_SETTINGS_TOGGLE,
+ CUJ_SHADE_DIALOG_OPEN,
+ CUJ_USER_DIALOG_OPEN,
+ CUJ_TASKBAR_EXPAND,
+ CUJ_TASKBAR_COLLAPSE,
+ CUJ_SHADE_CLEAR_ALL,
+ CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
+ CUJ_LOCKSCREEN_OCCLUSION,
+ CUJ_RECENTS_SCROLLING,
+ CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS,
+ CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE,
+ CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
+ CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION,
+ CUJ_LAUNCHER_OPEN_SEARCH_RESULT,
+ CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK,
+ CUJ_IME_INSETS_SHOW_ANIMATION,
+ CUJ_IME_INSETS_HIDE_ANIMATION,
+ CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER,
+ CUJ_LAUNCHER_UNFOLD_ANIM,
+ CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY,
+ CUJ_PREDICTIVE_BACK_CROSS_TASK,
+ CUJ_PREDICTIVE_BACK_HOME,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CujType {
+ }
+
+ private static final int NO_STATSD_LOGGING = -1;
+
+ // Used to convert CujType to InteractionType enum value for statsd logging.
+ // Use NO_STATSD_LOGGING in case the measurement for a given CUJ should not be logged to statsd.
+ private static final int[] CUJ_TO_STATSD_INTERACTION_TYPE = new int[LAST_CUJ + 1];
+ static {
+ Arrays.fill(CUJ_TO_STATSD_INTERACTION_TYPE, NO_STATSD_LOGGING);
+
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_SCROLL_FLING] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_ROW_EXPAND] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_ROW_SWIPE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_EXPAND_COLLAPSE;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_SCROLL_SWIPE;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_PIP] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_QUICK_SWITCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_HEADS_UP_APPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_ADD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_REMOVE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_REMOVE;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_APP_START] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PASSWORD_APPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_APPEAR;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PATTERN_APPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_APPEAR;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PIN_APPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_APPEAR;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_DISAPPEAR;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PATTERN_DISAPPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_DISAPPEAR;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PIN_DISAPPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_DISAPPEAR;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_TRANSITION_FROM_AOD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_FROM_AOD;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_TRANSITION_TO_AOD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_TO_AOD;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_ALL_APPS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_ALL_APPS_SCROLL] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_PAGE_SCROLL] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_UNLOCK_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PIP_TRANSITION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_USER_SWITCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLASHSCREEN_AVD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLASHSCREEN_EXIT_ANIM] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SCREEN_OFF] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SCREEN_OFF_SHOW_AOD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_ONE_HANDED_ENTER_TRANSITION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_ONE_HANDED_EXIT_TRANSITION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_UNFOLD_ANIM] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_TO_NEXT_FLOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_NEXT_FLOW;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_SCREEN_FOR_STATUS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_SCREEN_FOR_STATUS;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_ENTER] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_ENTER;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_EXIT] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_EXIT;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_LAUNCH_CAMERA] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_RESIZE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_RESIZE;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_SLIDER] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_SLIDER;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TAKE_SCREENSHOT] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TAKE_SCREENSHOT;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_VOLUME_CONTROL] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__VOLUME_CONTROL;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_BIOMETRIC_PROMPT_TRANSITION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_TOGGLE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_TOGGLE;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_DIALOG_OPEN] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_USER_DIALOG_OPEN] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_DIALOG_OPEN;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TASKBAR_EXPAND] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_EXPAND;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TASKBAR_COLLAPSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_COLLAPSE;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_CLEAR_ALL] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_OCCLUSION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_RECENTS_SCROLLING] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_SEARCH_RESULT] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_SHOW_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_HIDE_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNFOLD_ANIM] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_TASK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_HOME] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME;
+ }
+
+ private Cuj() {
+ }
+
+ /**
+ * A helper method to translate CUJ type to CUJ name.
+ *
+ * @param cujType the cuj type defined in this file
+ * @return the name of the cuj type
+ */
+ public static String getNameOfCuj(int cujType) {
+ // Please note:
+ // 1. The length of the returned string shouldn't exceed MAX_LENGTH_OF_CUJ_NAME.
+ // 2. The returned string should be the same with the name defined in atoms.proto.
+ switch (cujType) {
+ case CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE:
+ return "NOTIFICATION_SHADE_EXPAND_COLLAPSE";
+ case CUJ_NOTIFICATION_SHADE_SCROLL_FLING:
+ return "NOTIFICATION_SHADE_SCROLL_FLING";
+ case CUJ_NOTIFICATION_SHADE_ROW_EXPAND:
+ return "NOTIFICATION_SHADE_ROW_EXPAND";
+ case CUJ_NOTIFICATION_SHADE_ROW_SWIPE:
+ return "NOTIFICATION_SHADE_ROW_SWIPE";
+ case CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE:
+ return "NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE";
+ case CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE:
+ return "NOTIFICATION_SHADE_QS_SCROLL_SWIPE";
+ case CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS:
+ return "LAUNCHER_APP_LAUNCH_FROM_RECENTS";
+ case CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON:
+ return "LAUNCHER_APP_LAUNCH_FROM_ICON";
+ case CUJ_LAUNCHER_APP_CLOSE_TO_HOME:
+ return "LAUNCHER_APP_CLOSE_TO_HOME";
+ case CUJ_LAUNCHER_APP_CLOSE_TO_PIP:
+ return "LAUNCHER_APP_CLOSE_TO_PIP";
+ case CUJ_LAUNCHER_QUICK_SWITCH:
+ return "LAUNCHER_QUICK_SWITCH";
+ case CUJ_NOTIFICATION_HEADS_UP_APPEAR:
+ return "NOTIFICATION_HEADS_UP_APPEAR";
+ case CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR:
+ return "NOTIFICATION_HEADS_UP_DISAPPEAR";
+ case CUJ_NOTIFICATION_ADD:
+ return "NOTIFICATION_ADD";
+ case CUJ_NOTIFICATION_REMOVE:
+ return "NOTIFICATION_REMOVE";
+ case CUJ_NOTIFICATION_APP_START:
+ return "NOTIFICATION_APP_START";
+ case CUJ_LOCKSCREEN_PASSWORD_APPEAR:
+ return "LOCKSCREEN_PASSWORD_APPEAR";
+ case CUJ_LOCKSCREEN_PATTERN_APPEAR:
+ return "LOCKSCREEN_PATTERN_APPEAR";
+ case CUJ_LOCKSCREEN_PIN_APPEAR:
+ return "LOCKSCREEN_PIN_APPEAR";
+ case CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR:
+ return "LOCKSCREEN_PASSWORD_DISAPPEAR";
+ case CUJ_LOCKSCREEN_PATTERN_DISAPPEAR:
+ return "LOCKSCREEN_PATTERN_DISAPPEAR";
+ case CUJ_LOCKSCREEN_PIN_DISAPPEAR:
+ return "LOCKSCREEN_PIN_DISAPPEAR";
+ case CUJ_LOCKSCREEN_TRANSITION_FROM_AOD:
+ return "LOCKSCREEN_TRANSITION_FROM_AOD";
+ case CUJ_LOCKSCREEN_TRANSITION_TO_AOD:
+ return "LOCKSCREEN_TRANSITION_TO_AOD";
+ case CUJ_LAUNCHER_OPEN_ALL_APPS :
+ return "LAUNCHER_OPEN_ALL_APPS";
+ case CUJ_LAUNCHER_ALL_APPS_SCROLL:
+ return "LAUNCHER_ALL_APPS_SCROLL";
+ case CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET:
+ return "LAUNCHER_APP_LAUNCH_FROM_WIDGET";
+ case CUJ_SETTINGS_PAGE_SCROLL:
+ return "SETTINGS_PAGE_SCROLL";
+ case CUJ_LOCKSCREEN_UNLOCK_ANIMATION:
+ return "LOCKSCREEN_UNLOCK_ANIMATION";
+ case CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON:
+ return "SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON";
+ case CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER:
+ return "SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER";
+ case CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE:
+ return "SHADE_APP_LAUNCH_FROM_QS_TILE";
+ case CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON:
+ return "SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON";
+ case CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP:
+ return "STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP";
+ case CUJ_PIP_TRANSITION:
+ return "PIP_TRANSITION";
+ case CUJ_WALLPAPER_TRANSITION:
+ return "WALLPAPER_TRANSITION";
+ case CUJ_USER_SWITCH:
+ return "USER_SWITCH";
+ case CUJ_SPLASHSCREEN_AVD:
+ return "SPLASHSCREEN_AVD";
+ case CUJ_SPLASHSCREEN_EXIT_ANIM:
+ return "SPLASHSCREEN_EXIT_ANIM";
+ case CUJ_SCREEN_OFF:
+ return "SCREEN_OFF";
+ case CUJ_SCREEN_OFF_SHOW_AOD:
+ return "SCREEN_OFF_SHOW_AOD";
+ case CUJ_ONE_HANDED_ENTER_TRANSITION:
+ return "ONE_HANDED_ENTER_TRANSITION";
+ case CUJ_ONE_HANDED_EXIT_TRANSITION:
+ return "ONE_HANDED_EXIT_TRANSITION";
+ case CUJ_UNFOLD_ANIM:
+ return "UNFOLD_ANIM";
+ case CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS:
+ return "SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS";
+ case CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS:
+ return "SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS";
+ case CUJ_SUW_LOADING_TO_NEXT_FLOW:
+ return "SUW_LOADING_TO_NEXT_FLOW";
+ case CUJ_SUW_LOADING_SCREEN_FOR_STATUS:
+ return "SUW_LOADING_SCREEN_FOR_STATUS";
+ case CUJ_SPLIT_SCREEN_ENTER:
+ return "SPLIT_SCREEN_ENTER";
+ case CUJ_SPLIT_SCREEN_EXIT:
+ return "SPLIT_SCREEN_EXIT";
+ case CUJ_LOCKSCREEN_LAUNCH_CAMERA:
+ return "LOCKSCREEN_LAUNCH_CAMERA";
+ case CUJ_SPLIT_SCREEN_RESIZE:
+ return "SPLIT_SCREEN_RESIZE";
+ case CUJ_SETTINGS_SLIDER:
+ return "SETTINGS_SLIDER";
+ case CUJ_TAKE_SCREENSHOT:
+ return "TAKE_SCREENSHOT";
+ case CUJ_VOLUME_CONTROL:
+ return "VOLUME_CONTROL";
+ case CUJ_BIOMETRIC_PROMPT_TRANSITION:
+ return "BIOMETRIC_PROMPT_TRANSITION";
+ case CUJ_SETTINGS_TOGGLE:
+ return "SETTINGS_TOGGLE";
+ case CUJ_SHADE_DIALOG_OPEN:
+ return "SHADE_DIALOG_OPEN";
+ case CUJ_USER_DIALOG_OPEN:
+ return "USER_DIALOG_OPEN";
+ case CUJ_TASKBAR_EXPAND:
+ return "TASKBAR_EXPAND";
+ case CUJ_TASKBAR_COLLAPSE:
+ return "TASKBAR_COLLAPSE";
+ case CUJ_SHADE_CLEAR_ALL:
+ return "SHADE_CLEAR_ALL";
+ case CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION:
+ return "LAUNCHER_UNLOCK_ENTRANCE_ANIMATION";
+ case CUJ_LOCKSCREEN_OCCLUSION:
+ return "LOCKSCREEN_OCCLUSION";
+ case CUJ_RECENTS_SCROLLING:
+ return "RECENTS_SCROLLING";
+ case CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS:
+ return "LAUNCHER_APP_SWIPE_TO_RECENTS";
+ case CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE:
+ return "LAUNCHER_CLOSE_ALL_APPS_SWIPE";
+ case CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME:
+ return "LAUNCHER_CLOSE_ALL_APPS_TO_HOME";
+ case CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION:
+ return "LOCKSCREEN_CLOCK_MOVE_ANIMATION";
+ case CUJ_LAUNCHER_OPEN_SEARCH_RESULT:
+ return "LAUNCHER_OPEN_SEARCH_RESULT";
+ case CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK:
+ return "LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK";
+ case CUJ_IME_INSETS_SHOW_ANIMATION:
+ return "IME_INSETS_SHOW_ANIMATION";
+ case CUJ_IME_INSETS_HIDE_ANIMATION:
+ return "IME_INSETS_HIDE_ANIMATION";
+ case CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER:
+ return "SPLIT_SCREEN_DOUBLE_TAP_DIVIDER";
+ case CUJ_LAUNCHER_UNFOLD_ANIM:
+ return "LAUNCHER_UNFOLD_ANIM";
+ case CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY:
+ return "PREDICTIVE_BACK_CROSS_ACTIVITY";
+ case CUJ_PREDICTIVE_BACK_CROSS_TASK:
+ return "PREDICTIVE_BACK_CROSS_TASK";
+ case CUJ_PREDICTIVE_BACK_HOME:
+ return "PREDICTIVE_BACK_HOME";
+ }
+ return "UNKNOWN";
+ }
+
+ public static int getStatsdInteractionType(@CujType int cujType) {
+ return CUJ_TO_STATSD_INTERACTION_TYPE[cujType];
+ }
+
+ /** Returns whether the measurements for the given CUJ should be written to statsd. */
+ public static boolean logToStatsd(@CujType int cujType) {
+ return getStatsdInteractionType(cujType) != NO_STATSD_LOGGING;
+ }
+
+ /**
+ * A helper method to translate interaction type to CUJ name.
+ *
+ * @param interactionType the interaction type defined in AtomsProto.java
+ * @return the name of the interaction type
+ */
+ public static String getNameOfInteraction(int interactionType) {
+ // There is an offset amount of 1 between cujType and interactionType.
+ return Cuj.getNameOfCuj(getCujTypeFromInteraction(interactionType));
+ }
+
+ /**
+ * A helper method to translate interaction type to CUJ type.
+ *
+ * @param interactionType the interaction type defined in AtomsProto.java
+ * @return the integer in {@link Cuj.CujType}
+ */
+ private static int getCujTypeFromInteraction(int interactionType) {
+ return interactionType - 1;
+ }
+}
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index c83452d..86729f7 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -53,7 +53,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.DisplayRefreshRate.RefreshRate;
import com.android.internal.jank.InteractionJankMonitor.Configuration;
-import com.android.internal.jank.InteractionJankMonitor.Session;
import com.android.internal.util.FrameworkStatsLog;
import java.lang.annotation.Retention;
@@ -67,7 +66,6 @@
public class FrameTracker extends SurfaceControl.OnJankDataListener
implements HardwareRendererObserver.OnFrameMetricsAvailableListener {
private static final String TAG = "FrameTracker";
- private static final boolean DEBUG = false;
private static final long INVALID_ID = -1;
public static final int NANOS_IN_MILLISECOND = 1_000_000;
@@ -93,20 +91,19 @@
REASON_CANCEL_NORMAL,
REASON_CANCEL_NOT_BEGUN,
REASON_CANCEL_SAME_VSYNC,
+ REASON_CANCEL_TIMEOUT,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Reasons {
}
- @VisibleForTesting
- public final InteractionJankMonitor mMonitor;
private final HardwareRendererObserver mObserver;
private final int mTraceThresholdMissedFrames;
private final int mTraceThresholdFrameTimeMillis;
private final ThreadedRendererWrapper mRendererWrapper;
private final FrameMetricsWrapper mMetricsWrapper;
private final SparseArray<JankInfo> mJankInfos = new SparseArray<>();
- private final Session mSession;
+ private final Configuration mConfig;
private final ViewRootWrapper mViewRoot;
private final SurfaceControlWrapper mSurfaceControlWrapper;
private final int mDisplayId;
@@ -197,19 +194,18 @@
}
}
- public FrameTracker(@NonNull InteractionJankMonitor monitor, @NonNull Session session,
- @NonNull Handler handler, @Nullable ThreadedRendererWrapper renderer,
+ public FrameTracker(@NonNull Configuration config,
+ @Nullable ThreadedRendererWrapper renderer,
@Nullable ViewRootWrapper viewRootWrapper,
@NonNull SurfaceControlWrapper surfaceControlWrapper,
@NonNull ChoreographerWrapper choreographer,
@Nullable FrameMetricsWrapper metrics,
@NonNull StatsLogWrapper statsLog,
int traceThresholdMissedFrames, int traceThresholdFrameTimeMillis,
- @Nullable FrameTrackerListener listener, @NonNull Configuration config) {
- mMonitor = monitor;
+ @Nullable FrameTrackerListener listener) {
mSurfaceOnly = config.isSurfaceOnly();
- mSession = session;
- mHandler = handler;
+ mConfig = config;
+ mHandler = config.getHandler();
mChoreographer = choreographer;
mSurfaceControlWrapper = surfaceControlWrapper;
mStatsLog = statsLog;
@@ -222,7 +218,7 @@
mObserver = mSurfaceOnly
? null
: new HardwareRendererObserver(this, mMetricsWrapper.getTiming(),
- handler, /* waitForPresentTime= */ false);
+ mHandler, /* waitForPresentTime= */ false);
mTraceThresholdMissedFrames = traceThresholdMissedFrames;
mTraceThresholdFrameTimeMillis = traceThresholdFrameTimeMillis;
@@ -242,7 +238,7 @@
mSurfaceChangedCallback = new ViewRootImpl.SurfaceChangedCallback() {
@Override
public void surfaceCreated(SurfaceControl.Transaction t) {
- getHandler().runWithScissors(() -> {
+ mHandler.runWithScissors(() -> {
if (mSurfaceControl == null) {
mSurfaceControl = mViewRoot.getSurfaceControl();
if (mBeginVsyncId != INVALID_ID) {
@@ -262,13 +258,7 @@
// Wait a while to give the system a chance for the remaining
// frames to arrive, then force finish the session.
- getHandler().postDelayed(() -> {
- if (DEBUG) {
- Log.d(TAG, "surfaceDestroyed: " + mSession.getName()
- + ", finalized=" + mMetricsFinalized
- + ", info=" + mJankInfos.size()
- + ", vsync=" + mBeginVsyncId);
- }
+ mHandler.postDelayed(() -> {
if (!mMetricsFinalized) {
end(REASON_END_SURFACE_DESTROYED);
finish();
@@ -282,11 +272,6 @@
}
}
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public Handler getHandler() {
- return mHandler;
- }
-
/**
* Begin a trace session of the CUJ.
*/
@@ -300,10 +285,6 @@
mBeginVsyncId = mDeferMonitoring ? currentVsync + 1 : currentVsync;
}
if (mSurfaceControl != null) {
- if (DEBUG) {
- Log.d(TAG, "begin: " + mSession.getName() + ", begin=" + mBeginVsyncId
- + ", defer=" + mDeferMonitoring + ", current=" + currentVsync);
- }
if (mDeferMonitoring && currentVsync < mBeginVsyncId) {
markEvent("FT#deferMonitoring", 0);
// Normal case, we begin the instrument from the very beginning,
@@ -314,11 +295,6 @@
// there is no need to skip the frame where the begin invocation happens.
beginInternal();
}
- } else {
- if (DEBUG) {
- Log.d(TAG, "begin: defer beginning since the surface is not ready for CUJ="
- + mSession.getName());
- }
}
}
@@ -336,8 +312,8 @@
return;
}
mTracingStarted = true;
- Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, mSession.getName(), mSession.getName(),
- (int) mBeginVsyncId);
+ String name = mConfig.getSessionName();
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, name, name, (int) mBeginVsyncId);
markEvent("FT#beginVsync", mBeginVsyncId);
markEvent("FT#layerId", mSurfaceControl.getLayerId());
mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl);
@@ -361,15 +337,10 @@
} else if (mEndVsyncId <= mBeginVsyncId) {
return cancel(REASON_CANCEL_SAME_VSYNC);
} else {
- if (DEBUG) {
- Log.d(TAG, "end: " + mSession.getName()
- + ", end=" + mEndVsyncId + ", reason=" + reason);
- }
+ final String name = mConfig.getSessionName();
markEvent("FT#end", reason);
markEvent("FT#endVsync", mEndVsyncId);
- Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, mSession.getName(),
- (int) mBeginVsyncId);
- mSession.setReason(reason);
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, name, (int) mBeginVsyncId);
// We don't remove observer here,
// will remove it when all the frame metrics in this duration are called back.
@@ -395,16 +366,16 @@
mFlushAttempts++;
} else {
mWaitForFinishTimedOut = () -> {
- Log.e(TAG, "force finish cuj, time out: " + mSession.getName());
+ Log.e(TAG, "force finish cuj, time out: " + name);
finish();
};
delay = TimeUnit.SECONDS.toMillis(10);
}
- getHandler().postDelayed(mWaitForFinishTimedOut, delay);
+ mHandler.postDelayed(mWaitForFinishTimedOut, delay);
}
};
- getHandler().postDelayed(mWaitForFinishTimedOut, FLUSH_DELAY_MILLISECOND);
- notifyCujEvent(ACTION_SESSION_END);
+ mHandler.postDelayed(mWaitForFinishTimedOut, FLUSH_DELAY_MILLISECOND);
+ notifyCujEvent(ACTION_SESSION_END, reason);
return true;
}
}
@@ -421,22 +392,16 @@
markEvent("FT#cancel", reason);
// We don't need to end the trace section if it has never begun.
if (mTracingStarted) {
- Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, mSession.getName(),
- (int) mBeginVsyncId);
+ Trace.asyncTraceForTrackEnd(
+ Trace.TRACE_TAG_APP, mConfig.getSessionName(), (int) mBeginVsyncId);
}
// Always remove the observers in cancel call to avoid leakage.
removeObservers();
- if (DEBUG) {
- Log.d(TAG, "cancel: " + mSession.getName() + ", begin=" + mBeginVsyncId
- + ", end=" + mEndVsyncId + ", reason=" + reason);
- }
-
- mSession.setReason(reason);
// Notify the listener the session has been cancelled.
// We don't notify the listeners if the session never begun.
- notifyCujEvent(ACTION_SESSION_CANCEL);
+ notifyCujEvent(ACTION_SESSION_CANCEL, reason);
return true;
}
@@ -455,13 +420,13 @@
"The length of the trace event description <%s> exceeds %d",
event, MAX_LENGTH_EVENT_DESC));
}
- Trace.instantForTrack(Trace.TRACE_TAG_APP, mSession.getName(), event);
+ Trace.instantForTrack(Trace.TRACE_TAG_APP, mConfig.getSessionName(), event);
}
}
- private void notifyCujEvent(String action) {
+ private void notifyCujEvent(String action, @Reasons int reason) {
if (mListener == null) return;
- mListener.onCujEvents(mSession, action);
+ mListener.onCujEvents(this, action, reason);
}
@Override
@@ -496,7 +461,7 @@
*/
@VisibleForTesting
public void postCallback(Runnable callback) {
- getHandler().post(callback);
+ mHandler.post(callback);
}
@Nullable
@@ -587,13 +552,15 @@
if (mMetricsFinalized || mCancelled) return;
mMetricsFinalized = true;
- getHandler().removeCallbacks(mWaitForFinishTimedOut);
+ mHandler.removeCallbacks(mWaitForFinishTimedOut);
mWaitForFinishTimedOut = null;
markEvent("FT#finish", mJankInfos.size());
// The tracing has been ended, remove the observer, see if need to trigger perfetto.
removeObservers();
+ final String name = mConfig.getSessionName();
+
int totalFramesCount = 0;
long maxFrameTimeNanos = 0;
int missedFramesCount = 0;
@@ -616,7 +583,7 @@
totalFramesCount++;
boolean missedFrame = false;
if ((info.jankType & JANK_APP_DEADLINE_MISSED) != 0) {
- Log.w(TAG, "Missed App frame:" + info + ", CUJ=" + mSession.getName());
+ Log.w(TAG, "Missed App frame:" + info + ", CUJ=" + name);
missedAppFramesCount++;
missedFrame = true;
}
@@ -625,7 +592,7 @@
|| (info.jankType & JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED) != 0
|| (info.jankType & SURFACE_FLINGER_SCHEDULING) != 0
|| (info.jankType & PREDICTION_ERROR) != 0) {
- Log.w(TAG, "Missed SF frame:" + info + ", CUJ=" + mSession.getName());
+ Log.w(TAG, "Missed SF frame:" + info + ", CUJ=" + name);
missedSfFramesCount++;
missedFrame = true;
}
@@ -646,7 +613,7 @@
if (!mSurfaceOnly && !info.hwuiCallbackFired) {
markEvent("FT#MissedHWUICallback", info.frameVsyncId);
Log.w(TAG, "Missing HWUI jank callback for vsyncId: " + info.frameVsyncId
- + ", CUJ=" + mSession.getName());
+ + ", CUJ=" + name);
}
}
if (!mSurfaceOnly && info.hwuiCallbackFired) {
@@ -654,7 +621,7 @@
if (!info.surfaceControlCallbackFired) {
markEvent("FT#MissedSFCallback", info.frameVsyncId);
Log.w(TAG, "Missing SF jank callback for vsyncId: " + info.frameVsyncId
- + ", CUJ=" + mSession.getName());
+ + ", CUJ=" + name);
}
}
}
@@ -662,29 +629,26 @@
maxSuccessiveMissedFramesCount, successiveMissedFramesCount);
// Log the frame stats as counters to make them easily accessible in traces.
- Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedFrames",
- missedFramesCount);
- Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedAppFrames",
- missedAppFramesCount);
- Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedSfFrames",
- missedSfFramesCount);
- Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#totalFrames",
- totalFramesCount);
- Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#maxFrameTimeMillis",
+ Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#missedFrames", missedFramesCount);
+ Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#missedAppFrames", missedAppFramesCount);
+ Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#missedSfFrames", missedSfFramesCount);
+ Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#totalFrames", totalFramesCount);
+ Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#maxFrameTimeMillis",
(int) (maxFrameTimeNanos / NANOS_IN_MILLISECOND));
- Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#maxSuccessiveMissedFrames",
+ Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#maxSuccessiveMissedFrames",
maxSuccessiveMissedFramesCount);
// Trigger perfetto if necessary.
- if (shouldTriggerPerfetto(missedFramesCount, (int) maxFrameTimeNanos)) {
- triggerPerfetto();
+ if (mListener != null
+ && shouldTriggerPerfetto(missedFramesCount, (int) maxFrameTimeNanos)) {
+ mListener.triggerPerfetto(mConfig);
}
- if (mSession.logToStatsd()) {
+ if (mConfig.logToStatsd()) {
mStatsLog.write(
FrameworkStatsLog.UI_INTERACTION_FRAME_INFO_REPORTED,
mDisplayId,
refreshRate,
- mSession.getStatsdInteractionType(),
+ mConfig.getStatsdInteractionType(),
totalFramesCount,
missedFramesCount,
maxFrameTimeNanos, /* will be 0 if mSurfaceOnly == true */
@@ -692,16 +656,6 @@
missedAppFramesCount,
maxSuccessiveMissedFramesCount);
}
- if (DEBUG) {
- Log.i(TAG, "finish: CUJ=" + mSession.getName()
- + " (" + mBeginVsyncId + "," + mEndVsyncId + ")"
- + " totalFrames=" + totalFramesCount
- + " missedAppFrames=" + missedAppFramesCount
- + " missedSfFrames=" + missedSfFramesCount
- + " missedFrames=" + missedFramesCount
- + " maxFrameTimeMillis=" + maxFrameTimeNanos / NANOS_IN_MILLISECOND
- + " maxSuccessiveMissedFramesCount=" + maxSuccessiveMissedFramesCount);
- }
}
ThreadedRendererWrapper getThreadedRenderer() {
@@ -737,13 +691,6 @@
}
/**
- * Trigger the prefetto daemon.
- */
- public void triggerPerfetto() {
- mMonitor.trigger(mSession);
- }
-
- /**
* A wrapper class that we can spy FrameMetrics (a final class) in unit tests.
*/
public static class FrameMetricsWrapper {
@@ -895,9 +842,17 @@
/**
* Notify that the CUJ session was created.
*
- * @param session the CUJ session
+ * @param tracker the tracker
* @param action the specific action
+ * @param reason the reason for the action
*/
- void onCujEvents(Session session, String action);
+ void onCujEvents(FrameTracker tracker, String action, @Reasons int reason);
+
+ /**
+ * Notify that the Perfetto trace should be triggered.
+ *
+ * @param config the tracker configuration
+ */
+ void triggerPerfetto(Configuration config);
}
}
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index e6b036c..8b1879f 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -23,89 +23,9 @@
import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NORMAL;
import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT;
import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
-import static com.android.internal.jank.FrameTracker.REASON_END_UNKNOWN;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_APPEAR;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_DISAPPEAR;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_APPEAR;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_DISAPPEAR;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_APPEAR;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_DISAPPEAR;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_FROM_AOD;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_TO_AOD;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_SLIDER;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_TOGGLE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_REMOVE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_EXPAND_COLLAPSE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_SCROLL_SWIPE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_ENTER;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_EXIT;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_RESIZE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_SCREEN_FOR_STATUS;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_NEXT_FLOW;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TAKE_SCREENSHOT;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_COLLAPSE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_EXPAND;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_DIALOG_OPEN;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__VOLUME_CONTROL;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION;
import android.Manifest;
import android.annotation.ColorInt;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.UiThread;
@@ -137,34 +57,31 @@
import com.android.internal.jank.FrameTracker.ViewRootWrapper;
import com.android.internal.util.PerfettoTrigger;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.time.Instant;
-import java.util.Locale;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
/**
- * This class let users to begin and end the always on tracing mechanism.
+ * This class lets users begin and end the always on tracing mechanism.
*
* Enabling for local development:
- *
+ *<pre>
* adb shell device_config put interaction_jank_monitor enabled true
* adb shell device_config put interaction_jank_monitor sampling_interval 1
- *
+ * </pre>
* On debuggable builds, an overlay can be used to display the name of the
* currently running cuj using:
- *
+ * <pre>
* adb shell device_config put interaction_jank_monitor debug_overlay_enabled true
- *
- * NOTE: The overlay will interfere with metrics, so it should only be used
- * for understanding which UI events correspeond to which CUJs.
+ * </pre>
+ * <b>NOTE</b>: The overlay will interfere with metrics, so it should only be used
+ * for understanding which UI events correspond to which CUJs.
*
* @hide
*/
public class InteractionJankMonitor {
private static final String TAG = InteractionJankMonitor.class.getSimpleName();
- private static final boolean DEBUG = false;
private static final String ACTION_PREFIX = InteractionJankMonitor.class.getCanonicalName();
private static final String DEFAULT_WORKER_NAME = TAG + "-Worker";
@@ -186,218 +103,79 @@
private static final int DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS = 64;
private static final boolean DEFAULT_DEBUG_OVERLAY_ENABLED = false;
- @VisibleForTesting
- public static final int MAX_LENGTH_OF_CUJ_NAME = 80;
private static final int MAX_LENGTH_SESSION_NAME = 100;
public static final String ACTION_SESSION_END = ACTION_PREFIX + ".ACTION_SESSION_END";
public static final String ACTION_SESSION_CANCEL = ACTION_PREFIX + ".ACTION_SESSION_CANCEL";
- // Every value must have a corresponding entry in CUJ_STATSD_INTERACTION_TYPE.
- public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = 0;
- public static final int CUJ_NOTIFICATION_SHADE_SCROLL_FLING = 2;
- public static final int CUJ_NOTIFICATION_SHADE_ROW_EXPAND = 3;
- public static final int CUJ_NOTIFICATION_SHADE_ROW_SWIPE = 4;
- public static final int CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE = 5;
- public static final int CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE = 6;
- public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS = 7;
- public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON = 8;
- public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME = 9;
- public static final int CUJ_LAUNCHER_APP_CLOSE_TO_PIP = 10;
- public static final int CUJ_LAUNCHER_QUICK_SWITCH = 11;
- public static final int CUJ_NOTIFICATION_HEADS_UP_APPEAR = 12;
- public static final int CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR = 13;
- public static final int CUJ_NOTIFICATION_ADD = 14;
- public static final int CUJ_NOTIFICATION_REMOVE = 15;
- public static final int CUJ_NOTIFICATION_APP_START = 16;
- public static final int CUJ_LOCKSCREEN_PASSWORD_APPEAR = 17;
- public static final int CUJ_LOCKSCREEN_PATTERN_APPEAR = 18;
- public static final int CUJ_LOCKSCREEN_PIN_APPEAR = 19;
- public static final int CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR = 20;
- public static final int CUJ_LOCKSCREEN_PATTERN_DISAPPEAR = 21;
- public static final int CUJ_LOCKSCREEN_PIN_DISAPPEAR = 22;
- public static final int CUJ_LOCKSCREEN_TRANSITION_FROM_AOD = 23;
- public static final int CUJ_LOCKSCREEN_TRANSITION_TO_AOD = 24;
- public static final int CUJ_LAUNCHER_OPEN_ALL_APPS = 25;
- public static final int CUJ_LAUNCHER_ALL_APPS_SCROLL = 26;
- public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET = 27;
- public static final int CUJ_SETTINGS_PAGE_SCROLL = 28;
- public static final int CUJ_LOCKSCREEN_UNLOCK_ANIMATION = 29;
- public static final int CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON = 30;
- public static final int CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER = 31;
- public static final int CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE = 32;
- public static final int CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON = 33;
- public static final int CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP = 34;
- public static final int CUJ_PIP_TRANSITION = 35;
- public static final int CUJ_WALLPAPER_TRANSITION = 36;
- public static final int CUJ_USER_SWITCH = 37;
- public static final int CUJ_SPLASHSCREEN_AVD = 38;
- public static final int CUJ_SPLASHSCREEN_EXIT_ANIM = 39;
- public static final int CUJ_SCREEN_OFF = 40;
- public static final int CUJ_SCREEN_OFF_SHOW_AOD = 41;
- public static final int CUJ_ONE_HANDED_ENTER_TRANSITION = 42;
- public static final int CUJ_ONE_HANDED_EXIT_TRANSITION = 43;
- public static final int CUJ_UNFOLD_ANIM = 44;
- public static final int CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS = 45;
- public static final int CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS = 46;
- public static final int CUJ_SUW_LOADING_TO_NEXT_FLOW = 47;
- public static final int CUJ_SUW_LOADING_SCREEN_FOR_STATUS = 48;
- public static final int CUJ_SPLIT_SCREEN_ENTER = 49;
- public static final int CUJ_SPLIT_SCREEN_EXIT = 50;
- public static final int CUJ_LOCKSCREEN_LAUNCH_CAMERA = 51; // reserved.
- public static final int CUJ_SPLIT_SCREEN_RESIZE = 52;
- public static final int CUJ_SETTINGS_SLIDER = 53;
- public static final int CUJ_TAKE_SCREENSHOT = 54;
- public static final int CUJ_VOLUME_CONTROL = 55;
- public static final int CUJ_BIOMETRIC_PROMPT_TRANSITION = 56;
- public static final int CUJ_SETTINGS_TOGGLE = 57;
- public static final int CUJ_SHADE_DIALOG_OPEN = 58;
- public static final int CUJ_USER_DIALOG_OPEN = 59;
- public static final int CUJ_TASKBAR_EXPAND = 60;
- public static final int CUJ_TASKBAR_COLLAPSE = 61;
- public static final int CUJ_SHADE_CLEAR_ALL = 62;
- public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION = 63;
- public static final int CUJ_LOCKSCREEN_OCCLUSION = 64;
- public static final int CUJ_RECENTS_SCROLLING = 65;
- public static final int CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS = 66;
- public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE = 67;
- public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME = 68;
- public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = 70;
- public static final int CUJ_LAUNCHER_OPEN_SEARCH_RESULT = 71;
- // 72 - 77 are reserved for b/281564325.
-
- /**
- * In some cases when we do not have any end-target, we play a simple slide-down animation.
- * eg: Open an app from Overview/Task switcher such that there is no home-screen icon.
- * eg: Exit the app using back gesture.
- */
- public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK = 78;
- public static final int CUJ_IME_INSETS_SHOW_ANIMATION = 80;
- public static final int CUJ_IME_INSETS_HIDE_ANIMATION = 81;
-
- public static final int CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER = 82;
-
- public static final int CUJ_LAUNCHER_UNFOLD_ANIM = 83;
-
- public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = 84;
- public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = 85;
- public static final int CUJ_PREDICTIVE_BACK_HOME = 86;
-
- private static final int LAST_CUJ = CUJ_PREDICTIVE_BACK_HOME;
- private static final int NO_STATSD_LOGGING = -1;
-
- // Used to convert CujType to InteractionType enum value for statsd logging.
- // Use NO_STATSD_LOGGING in case the measurement for a given CUJ should not be logged to statsd.
- @VisibleForTesting
- public static final int[] CUJ_TO_STATSD_INTERACTION_TYPE = new int[LAST_CUJ + 1];
-
- static {
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE;
- CUJ_TO_STATSD_INTERACTION_TYPE[1] = NO_STATSD_LOGGING; // This is deprecated.
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_SCROLL_FLING] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_ROW_EXPAND] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_ROW_SWIPE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_EXPAND_COLLAPSE;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_SCROLL_SWIPE;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_PIP] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_QUICK_SWITCH] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_HEADS_UP_APPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_ADD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_REMOVE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_REMOVE;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_APP_START] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PASSWORD_APPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_APPEAR;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PATTERN_APPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_APPEAR;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PIN_APPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_APPEAR;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_DISAPPEAR;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PATTERN_DISAPPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_DISAPPEAR;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PIN_DISAPPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_DISAPPEAR;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_TRANSITION_FROM_AOD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_FROM_AOD;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_TRANSITION_TO_AOD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_TO_AOD;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_ALL_APPS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_ALL_APPS_SCROLL] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_PAGE_SCROLL] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_UNLOCK_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PIP_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_USER_SWITCH] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLASHSCREEN_AVD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLASHSCREEN_EXIT_ANIM] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SCREEN_OFF] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SCREEN_OFF_SHOW_AOD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_ONE_HANDED_ENTER_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_ONE_HANDED_EXIT_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_UNFOLD_ANIM] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_TO_NEXT_FLOW] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_NEXT_FLOW;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_SCREEN_FOR_STATUS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_SCREEN_FOR_STATUS;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_ENTER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_ENTER;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_EXIT] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_EXIT;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_LAUNCH_CAMERA] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_RESIZE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_RESIZE;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_SLIDER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_SLIDER;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TAKE_SCREENSHOT] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TAKE_SCREENSHOT;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_VOLUME_CONTROL] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__VOLUME_CONTROL;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_BIOMETRIC_PROMPT_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_TOGGLE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_TOGGLE;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_DIALOG_OPEN] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_USER_DIALOG_OPEN] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_DIALOG_OPEN;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TASKBAR_EXPAND] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_EXPAND;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TASKBAR_COLLAPSE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_COLLAPSE;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_CLEAR_ALL] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_OCCLUSION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_RECENTS_SCROLLING] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME;
- CUJ_TO_STATSD_INTERACTION_TYPE[69] = NO_STATSD_LOGGING; // This is deprecated.
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_SEARCH_RESULT] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT;
- // 72 - 77 are reserved for b/281564325.
- CUJ_TO_STATSD_INTERACTION_TYPE[72] = NO_STATSD_LOGGING;
- CUJ_TO_STATSD_INTERACTION_TYPE[73] = NO_STATSD_LOGGING;
- CUJ_TO_STATSD_INTERACTION_TYPE[74] = NO_STATSD_LOGGING;
- CUJ_TO_STATSD_INTERACTION_TYPE[75] = NO_STATSD_LOGGING;
- CUJ_TO_STATSD_INTERACTION_TYPE[76] = NO_STATSD_LOGGING;
- CUJ_TO_STATSD_INTERACTION_TYPE[77] = NO_STATSD_LOGGING;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
- CUJ_TO_STATSD_INTERACTION_TYPE[79] = NO_STATSD_LOGGING; // This is deprecated.
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_SHOW_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_HIDE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNFOLD_ANIM] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY] =
- UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_TASK] =
- UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_HOME] =
- UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME;
- }
+ // These are not the CUJ constants you are looking for. These constants simply forward their
+ // definition from {@link Cuj}. They are here only as a transition measure until all references
+ // have been updated to the new location.
+ @Deprecated public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
+ @Deprecated public static final int CUJ_NOTIFICATION_SHADE_SCROLL_FLING = Cuj.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
+ @Deprecated public static final int CUJ_NOTIFICATION_SHADE_ROW_EXPAND = Cuj.CUJ_NOTIFICATION_SHADE_ROW_EXPAND;
+ @Deprecated public static final int CUJ_NOTIFICATION_SHADE_ROW_SWIPE = Cuj.CUJ_NOTIFICATION_SHADE_ROW_SWIPE;
+ @Deprecated public static final int CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
+ @Deprecated public static final int CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE = Cuj.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE;
+ @Deprecated public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME = Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME;
+ @Deprecated public static final int CUJ_LAUNCHER_QUICK_SWITCH = Cuj.CUJ_LAUNCHER_QUICK_SWITCH;
+ @Deprecated public static final int CUJ_NOTIFICATION_HEADS_UP_APPEAR = Cuj.CUJ_NOTIFICATION_HEADS_UP_APPEAR;
+ @Deprecated public static final int CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR = Cuj.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR;
+ @Deprecated public static final int CUJ_NOTIFICATION_ADD = Cuj.CUJ_NOTIFICATION_ADD;
+ @Deprecated public static final int CUJ_NOTIFICATION_REMOVE = Cuj.CUJ_NOTIFICATION_REMOVE;
+ @Deprecated public static final int CUJ_NOTIFICATION_APP_START = Cuj.CUJ_NOTIFICATION_APP_START;
+ @Deprecated public static final int CUJ_LOCKSCREEN_PASSWORD_APPEAR = Cuj.CUJ_LOCKSCREEN_PASSWORD_APPEAR;
+ @Deprecated public static final int CUJ_LOCKSCREEN_PATTERN_APPEAR = Cuj.CUJ_LOCKSCREEN_PATTERN_APPEAR;
+ @Deprecated public static final int CUJ_LOCKSCREEN_PIN_APPEAR = Cuj.CUJ_LOCKSCREEN_PIN_APPEAR;
+ @Deprecated public static final int CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR = Cuj.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR;
+ @Deprecated public static final int CUJ_LOCKSCREEN_PATTERN_DISAPPEAR = Cuj.CUJ_LOCKSCREEN_PATTERN_DISAPPEAR;
+ @Deprecated public static final int CUJ_LOCKSCREEN_PIN_DISAPPEAR = Cuj.CUJ_LOCKSCREEN_PIN_DISAPPEAR;
+ @Deprecated public static final int CUJ_LOCKSCREEN_TRANSITION_FROM_AOD = Cuj.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
+ @Deprecated public static final int CUJ_LOCKSCREEN_TRANSITION_TO_AOD = Cuj.CUJ_LOCKSCREEN_TRANSITION_TO_AOD;
+ @Deprecated public static final int CUJ_SETTINGS_PAGE_SCROLL = Cuj.CUJ_SETTINGS_PAGE_SCROLL;
+ @Deprecated public static final int CUJ_LOCKSCREEN_UNLOCK_ANIMATION = Cuj.CUJ_LOCKSCREEN_UNLOCK_ANIMATION;
+ @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON;
+ @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER;
+ @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE;
+ @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON;
+ @Deprecated public static final int CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP = Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP;
+ @Deprecated public static final int CUJ_PIP_TRANSITION = Cuj.CUJ_PIP_TRANSITION;
+ @Deprecated public static final int CUJ_USER_SWITCH = Cuj.CUJ_USER_SWITCH;
+ @Deprecated public static final int CUJ_SPLASHSCREEN_AVD = Cuj.CUJ_SPLASHSCREEN_AVD;
+ @Deprecated public static final int CUJ_SPLASHSCREEN_EXIT_ANIM = Cuj.CUJ_SPLASHSCREEN_EXIT_ANIM;
+ @Deprecated public static final int CUJ_SCREEN_OFF = Cuj.CUJ_SCREEN_OFF;
+ @Deprecated public static final int CUJ_SCREEN_OFF_SHOW_AOD = Cuj.CUJ_SCREEN_OFF_SHOW_AOD;
+ @Deprecated public static final int CUJ_UNFOLD_ANIM = Cuj.CUJ_UNFOLD_ANIM;
+ @Deprecated public static final int CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS = Cuj.CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS;
+ @Deprecated public static final int CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS = Cuj.CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS;
+ @Deprecated public static final int CUJ_SUW_LOADING_TO_NEXT_FLOW = Cuj.CUJ_SUW_LOADING_TO_NEXT_FLOW;
+ @Deprecated public static final int CUJ_SUW_LOADING_SCREEN_FOR_STATUS = Cuj.CUJ_SUW_LOADING_SCREEN_FOR_STATUS;
+ @Deprecated public static final int CUJ_SPLIT_SCREEN_RESIZE = Cuj.CUJ_SPLIT_SCREEN_RESIZE;
+ @Deprecated public static final int CUJ_SETTINGS_SLIDER = Cuj.CUJ_SETTINGS_SLIDER;
+ @Deprecated public static final int CUJ_TAKE_SCREENSHOT = Cuj.CUJ_TAKE_SCREENSHOT;
+ @Deprecated public static final int CUJ_VOLUME_CONTROL = Cuj.CUJ_VOLUME_CONTROL;
+ @Deprecated public static final int CUJ_BIOMETRIC_PROMPT_TRANSITION = Cuj.CUJ_BIOMETRIC_PROMPT_TRANSITION;
+ @Deprecated public static final int CUJ_SETTINGS_TOGGLE = Cuj.CUJ_SETTINGS_TOGGLE;
+ @Deprecated public static final int CUJ_SHADE_DIALOG_OPEN = Cuj.CUJ_SHADE_DIALOG_OPEN;
+ @Deprecated public static final int CUJ_USER_DIALOG_OPEN = Cuj.CUJ_USER_DIALOG_OPEN;
+ @Deprecated public static final int CUJ_TASKBAR_EXPAND = Cuj.CUJ_TASKBAR_EXPAND;
+ @Deprecated public static final int CUJ_TASKBAR_COLLAPSE = Cuj.CUJ_TASKBAR_COLLAPSE;
+ @Deprecated public static final int CUJ_SHADE_CLEAR_ALL = Cuj.CUJ_SHADE_CLEAR_ALL;
+ @Deprecated public static final int CUJ_LOCKSCREEN_OCCLUSION = Cuj.CUJ_LOCKSCREEN_OCCLUSION;
+ @Deprecated public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = Cuj.CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION;
+ @Deprecated public static final int CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER = Cuj.CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
+ @Deprecated public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
+ @Deprecated public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = Cuj.CUJ_PREDICTIVE_BACK_CROSS_TASK;
+ @Deprecated public static final int CUJ_PREDICTIVE_BACK_HOME = Cuj.CUJ_PREDICTIVE_BACK_HOME;
private static class InstanceHolder {
public static final InteractionJankMonitor INSTANCE =
new InteractionJankMonitor(new HandlerThread(DEFAULT_WORKER_NAME));
}
- private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener =
- this::updateProperties;
-
@GuardedBy("mLock")
- private final SparseArray<FrameTracker> mRunningTrackers;
- @GuardedBy("mLock")
- private final SparseArray<Runnable> mTimeoutActions;
- private final HandlerThread mWorker;
+ private final SparseArray<RunningTracker> mRunningTrackers = new SparseArray<>();
+ private final Handler mWorker;
private final DisplayResolutionTracker mDisplayResolutionTracker;
private final Object mLock = new Object();
private @ColorInt int mDebugBgColor = Color.CYAN;
@@ -409,91 +187,6 @@
private int mTraceThresholdMissedFrames = DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES;
private int mTraceThresholdFrameTimeMillis = DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS;
- /** @hide */
- @IntDef({
- CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
- CUJ_NOTIFICATION_SHADE_SCROLL_FLING,
- CUJ_NOTIFICATION_SHADE_ROW_EXPAND,
- CUJ_NOTIFICATION_SHADE_ROW_SWIPE,
- CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
- CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE,
- CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS,
- CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON,
- CUJ_LAUNCHER_APP_CLOSE_TO_HOME,
- CUJ_LAUNCHER_APP_CLOSE_TO_PIP,
- CUJ_LAUNCHER_QUICK_SWITCH,
- CUJ_NOTIFICATION_HEADS_UP_APPEAR,
- CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR,
- CUJ_NOTIFICATION_ADD,
- CUJ_NOTIFICATION_REMOVE,
- CUJ_NOTIFICATION_APP_START,
- CUJ_LOCKSCREEN_PASSWORD_APPEAR,
- CUJ_LOCKSCREEN_PATTERN_APPEAR,
- CUJ_LOCKSCREEN_PIN_APPEAR,
- CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR,
- CUJ_LOCKSCREEN_PATTERN_DISAPPEAR,
- CUJ_LOCKSCREEN_PIN_DISAPPEAR,
- CUJ_LOCKSCREEN_TRANSITION_FROM_AOD,
- CUJ_LOCKSCREEN_TRANSITION_TO_AOD,
- CUJ_LAUNCHER_OPEN_ALL_APPS,
- CUJ_LAUNCHER_ALL_APPS_SCROLL,
- CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET,
- CUJ_SETTINGS_PAGE_SCROLL,
- CUJ_LOCKSCREEN_UNLOCK_ANIMATION,
- CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON,
- CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER,
- CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
- CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON,
- CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
- CUJ_PIP_TRANSITION,
- CUJ_WALLPAPER_TRANSITION,
- CUJ_USER_SWITCH,
- CUJ_SPLASHSCREEN_AVD,
- CUJ_SPLASHSCREEN_EXIT_ANIM,
- CUJ_SCREEN_OFF,
- CUJ_SCREEN_OFF_SHOW_AOD,
- CUJ_ONE_HANDED_ENTER_TRANSITION,
- CUJ_ONE_HANDED_EXIT_TRANSITION,
- CUJ_UNFOLD_ANIM,
- CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS,
- CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS,
- CUJ_SUW_LOADING_TO_NEXT_FLOW,
- CUJ_SUW_LOADING_SCREEN_FOR_STATUS,
- CUJ_SPLIT_SCREEN_ENTER,
- CUJ_SPLIT_SCREEN_EXIT,
- CUJ_LOCKSCREEN_LAUNCH_CAMERA,
- CUJ_SPLIT_SCREEN_RESIZE,
- CUJ_SETTINGS_SLIDER,
- CUJ_TAKE_SCREENSHOT,
- CUJ_VOLUME_CONTROL,
- CUJ_BIOMETRIC_PROMPT_TRANSITION,
- CUJ_SETTINGS_TOGGLE,
- CUJ_SHADE_DIALOG_OPEN,
- CUJ_USER_DIALOG_OPEN,
- CUJ_TASKBAR_EXPAND,
- CUJ_TASKBAR_COLLAPSE,
- CUJ_SHADE_CLEAR_ALL,
- CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
- CUJ_LOCKSCREEN_OCCLUSION,
- CUJ_RECENTS_SCROLLING,
- CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS,
- CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE,
- CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
- CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION,
- CUJ_LAUNCHER_OPEN_SEARCH_RESULT,
- CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK,
- CUJ_IME_INSETS_SHOW_ANIMATION,
- CUJ_IME_INSETS_HIDE_ANIMATION,
- CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER,
- CUJ_LAUNCHER_UNFOLD_ANIM,
- CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY,
- CUJ_PREDICTIVE_BACK_CROSS_TASK,
- CUJ_PREDICTIVE_BACK_HOME,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface CujType {
- }
-
/**
* Get the singleton of InteractionJankMonitor.
*
@@ -511,71 +204,44 @@
@VisibleForTesting
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
public InteractionJankMonitor(@NonNull HandlerThread worker) {
- mRunningTrackers = new SparseArray<>();
- mTimeoutActions = new SparseArray<>();
- mWorker = worker;
- mWorker.start();
- mDisplayResolutionTracker = new DisplayResolutionTracker(worker.getThreadHandler());
- mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
- mEnabled = DEFAULT_ENABLED;
+ worker.start();
+ mWorker = worker.getThreadHandler();
+ mDisplayResolutionTracker = new DisplayResolutionTracker(mWorker);
final Context context = ActivityThread.currentApplication();
- if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) != PERMISSION_GRANTED) {
- if (DEBUG) {
- Log.d(TAG, "Initialized the InteractionJankMonitor."
- + " (No READ_DEVICE_CONFIG permission to change configs)"
- + " enabled=" + mEnabled + ", interval=" + mSamplingInterval
- + ", missedFrameThreshold=" + mTraceThresholdMissedFrames
- + ", frameTimeThreshold=" + mTraceThresholdFrameTimeMillis
- + ", package=" + context.getPackageName());
- }
+ if (context == null || context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) != PERMISSION_GRANTED) {
+ Log.w(TAG, "Initializing without READ_DEVICE_CONFIG permission."
+ + " enabled=" + mEnabled + ", interval=" + mSamplingInterval
+ + ", missedFrameThreshold=" + mTraceThresholdMissedFrames
+ + ", frameTimeThreshold=" + mTraceThresholdFrameTimeMillis
+ + ", package=" + (context == null ? "null" : context.getPackageName()));
return;
}
// Post initialization to the background in case we're running on the main thread.
- mWorker.getThreadHandler().post(
- () -> {
- try {
- mPropertiesChangedListener.onPropertiesChanged(
- DeviceConfig.getProperties(NAMESPACE_INTERACTION_JANK_MONITOR));
- DeviceConfig.addOnPropertiesChangedListener(
- NAMESPACE_INTERACTION_JANK_MONITOR,
- new HandlerExecutor(mWorker.getThreadHandler()),
- mPropertiesChangedListener);
- } catch (SecurityException ex) {
- Log.d(TAG, "Can't get properties: READ_DEVICE_CONFIG granted="
- + context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
- + ", package=" + context.getPackageName());
- }
- });
+ mWorker.post(() -> {
+ try {
+ updateProperties(DeviceConfig.getProperties(NAMESPACE_INTERACTION_JANK_MONITOR));
+ DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_INTERACTION_JANK_MONITOR,
+ new HandlerExecutor(mWorker), this::updateProperties);
+ } catch (SecurityException ex) {
+ Log.d(TAG, "Can't get properties: READ_DEVICE_CONFIG granted="
+ + context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
+ + ", package=" + context.getPackageName());
+ }
+ });
}
/**
* Creates a {@link FrameTracker} instance.
*
- * @param config the config used in instrumenting
- * @param session the session associates with this tracker
+ * @param config the conifg associates with this tracker
* @return instance of the FrameTracker
*/
@VisibleForTesting
- public FrameTracker createFrameTracker(Configuration config, Session session) {
+ public FrameTracker createFrameTracker(Configuration config) {
final View view = config.mView;
- if (!config.hasValidView()) {
- boolean attached = false;
- boolean hasViewRoot = false;
- boolean hasRenderer = false;
- if (view != null) {
- attached = view.isAttachedToWindow();
- hasViewRoot = view.getViewRootImpl() != null;
- hasRenderer = view.getThreadedRenderer() != null;
- }
- Log.d(TAG, "create FrameTracker fails: view=" + view
- + ", attached=" + attached + ", hasViewRoot=" + hasViewRoot
- + ", hasRenderer=" + hasRenderer, new Throwable());
- return null;
- }
-
final ThreadedRendererWrapper threadedRenderer =
view == null ? null : new ThreadedRendererWrapper(view.getThreadedRenderer());
final ViewRootWrapper viewRoot =
@@ -583,52 +249,50 @@
final SurfaceControlWrapper surfaceControl = new SurfaceControlWrapper();
final ChoreographerWrapper choreographer =
new ChoreographerWrapper(Choreographer.getInstance());
- final FrameTrackerListener eventsListener = (s, act) -> handleCujEvents(act, s);
+ final FrameTrackerListener eventsListener = new FrameTrackerListener() {
+ @Override
+ public void onCujEvents(FrameTracker tracker, String action, int reason) {
+ config.getHandler().runWithScissors(() ->
+ handleCujEvents(config.mCujType, tracker, action, reason),
+ EXECUTOR_TASK_TIMEOUT);
+ }
+
+ @Override
+ public void triggerPerfetto(Configuration config) {
+ mWorker.post(() -> PerfettoTrigger.trigger(config.getPerfettoTrigger()));
+ }
+ };
final FrameMetricsWrapper frameMetrics = new FrameMetricsWrapper();
- return new FrameTracker(this, session, config.getHandler(), threadedRenderer, viewRoot,
+ return new FrameTracker(config, threadedRenderer, viewRoot,
surfaceControl, choreographer, frameMetrics,
new FrameTracker.StatsLogWrapper(mDisplayResolutionTracker),
mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis,
- eventsListener, config);
+ eventsListener);
}
@UiThread
- private void handleCujEvents(String action, Session session) {
+ private void handleCujEvents(
+ @Cuj.CujType int cuj, FrameTracker tracker, String action, @Reasons int reason) {
// Clear the running and timeout tasks if the end / cancel was fired within the tracker.
// Or we might have memory leaks.
- if (needRemoveTasks(action, session)) {
- getTracker(session.getCuj()).getHandler().runWithScissors(() -> {
- removeTimeout(session.getCuj());
- removeTracker(session.getCuj(), session.getReason());
- }, EXECUTOR_TASK_TIMEOUT);
+ if (needRemoveTasks(action, reason)) {
+ removeTrackerIfCurrent(cuj, tracker, reason);
}
}
- private boolean needRemoveTasks(String action, Session session) {
- final boolean badEnd = action.equals(ACTION_SESSION_END)
- && session.getReason() != REASON_END_NORMAL;
+ private static boolean needRemoveTasks(String action, @Reasons int reason) {
+ final boolean badEnd = action.equals(ACTION_SESSION_END) && reason != REASON_END_NORMAL;
final boolean badCancel = action.equals(ACTION_SESSION_CANCEL)
- && !(session.getReason() == REASON_CANCEL_NORMAL
- || session.getReason() == REASON_CANCEL_TIMEOUT);
+ && !(reason == REASON_CANCEL_NORMAL || reason == REASON_CANCEL_TIMEOUT);
return badEnd || badCancel;
}
- private void removeTimeout(@CujType int cujType) {
- synchronized (mLock) {
- Runnable timeout = mTimeoutActions.get(cujType);
- if (timeout != null) {
- getTracker(cujType).getHandler().removeCallbacks(timeout);
- mTimeoutActions.remove(cujType);
- }
- }
- }
-
/**
* @param cujType cuj type
* @return true if the cuj is under instrumenting, false otherwise.
*/
- public boolean isInstrumenting(@CujType int cujType) {
+ public boolean isInstrumenting(@Cuj.CujType int cujType) {
synchronized (mLock) {
return mRunningTrackers.contains(cujType);
}
@@ -638,10 +302,10 @@
* Begins a trace session.
*
* @param v an attached view.
- * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+ * @param cujType the specific {@link Cuj.CujType}.
* @return boolean true if the tracker is started successfully, false otherwise.
*/
- public boolean begin(View v, @CujType int cujType) {
+ public boolean begin(View v, @Cuj.CujType int cujType) {
try {
return begin(Configuration.Builder.withView(cujType, v));
} catch (IllegalArgumentException ex) {
@@ -667,7 +331,7 @@
final boolean success = config.getHandler().runWithScissors(
() -> result.mResult = beginInternal(config), EXECUTOR_TASK_TIMEOUT);
if (!success) {
- Log.d(TAG, "begin failed due to timeout, CUJ=" + getNameOfCuj(config.mCujType));
+ Log.d(TAG, "begin failed due to timeout, CUJ=" + Cuj.getNameOfCuj(config.mCujType));
return false;
}
return result.mResult;
@@ -680,75 +344,59 @@
@UiThread
private boolean beginInternal(@NonNull Configuration conf) {
int cujType = conf.mCujType;
- if (!shouldMonitor(cujType)) return false;
- FrameTracker tracker = getTracker(cujType);
- // Skip subsequent calls if we already have an ongoing tracing.
- if (tracker != null) return false;
+ if (!shouldMonitor()) {
+ return false;
+ }
- // begin a new trace session.
- tracker = createFrameTracker(conf, new Session(cujType, conf.mTag));
- if (tracker == null) return false;
- putTracker(cujType, tracker);
- tracker.begin();
+ RunningTracker tracker = putTrackerIfNoCurrent(cujType, () ->
+ new RunningTracker(
+ conf, createFrameTracker(conf), () -> cancel(cujType, REASON_CANCEL_TIMEOUT)));
+ if (tracker == null) {
+ return false;
+ }
+ tracker.mTracker.begin();
// Cancel the trace if we don't get an end() call in specified duration.
- scheduleTimeoutAction(
- cujType, conf.mTimeout, () -> cancel(cujType, REASON_CANCEL_TIMEOUT));
+ scheduleTimeoutAction(tracker.mConfig, tracker.mTimeoutAction);
+
return true;
}
/**
* Check if the monitoring is enabled and if it should be sampled.
*/
- @SuppressWarnings("RandomModInteger")
@VisibleForTesting
- public boolean shouldMonitor(@CujType int cujType) {
- boolean shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0;
- if (!mEnabled || !shouldSample) {
- if (DEBUG) {
- Log.d(TAG, "Skip monitoring cuj: " + getNameOfCuj(cujType)
- + ", enable=" + mEnabled + ", debuggable=" + DEFAULT_ENABLED
- + ", sample=" + shouldSample + ", interval=" + mSamplingInterval);
- }
- return false;
- }
- return true;
+ public boolean shouldMonitor() {
+ return mEnabled && (ThreadLocalRandom.current().nextInt(mSamplingInterval) == 0);
}
- /**
- * Schedules a timeout action.
- * @param cuj cuj type
- * @param timeout duration to timeout
- * @param action action once timeout
- */
@VisibleForTesting
- public void scheduleTimeoutAction(@CujType int cuj, long timeout, Runnable action) {
- synchronized (mLock) {
- mTimeoutActions.put(cuj, action);
- getTracker(cuj).getHandler().postDelayed(action, timeout);
- }
+ public void scheduleTimeoutAction(Configuration config, Runnable action) {
+ config.getHandler().postDelayed(action, config.mTimeout);
}
/**
* Ends a trace session.
*
- * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+ * @param cujType the specific {@link Cuj.CujType}.
* @return boolean true if the tracker is ended successfully, false otherwise.
*/
- public boolean end(@CujType int cujType) {
+ public boolean end(@Cuj.CujType int cujType) {
postEventLogToWorkerThread((unixNanos, elapsedNanos, realtimeNanos) -> {
EventLogTags.writeJankCujEventsEndRequest(
cujType, unixNanos, elapsedNanos, realtimeNanos);
});
- FrameTracker tracker = getTracker(cujType);
+ RunningTracker tracker = getTracker(cujType);
// Skip this call since we haven't started a trace yet.
- if (tracker == null) return false;
+ if (tracker == null) {
+ return false;
+ }
try {
final TrackerResult result = new TrackerResult();
- final boolean success = tracker.getHandler().runWithScissors(
- () -> result.mResult = endInternal(cujType), EXECUTOR_TASK_TIMEOUT);
+ final boolean success = tracker.mConfig.getHandler().runWithScissors(
+ () -> result.mResult = endInternal(tracker), EXECUTOR_TASK_TIMEOUT);
if (!success) {
- Log.d(TAG, "end failed due to timeout, CUJ=" + getNameOfCuj(cujType));
+ Log.d(TAG, "end failed due to timeout, CUJ=" + Cuj.getNameOfCuj(cujType));
return false;
}
return result.mResult;
@@ -759,15 +407,11 @@
}
@UiThread
- private boolean endInternal(@CujType int cujType) {
- // remove the timeout action first.
- removeTimeout(cujType);
- FrameTracker tracker = getTracker(cujType);
- if (tracker == null) return false;
- // if the end call doesn't return true, another thread is handling end of the cuj.
- if (tracker.end(REASON_END_NORMAL)) {
- removeTracker(cujType, REASON_END_NORMAL);
+ private boolean endInternal(RunningTracker tracker) {
+ if (removeTrackerIfCurrent(tracker, REASON_END_NORMAL)) {
+ return false;
}
+ tracker.mTracker.end(REASON_END_NORMAL);
return true;
}
@@ -776,7 +420,7 @@
*
* @return boolean true if the tracker is cancelled successfully, false otherwise.
*/
- public boolean cancel(@CujType int cujType) {
+ public boolean cancel(@Cuj.CujType int cujType) {
postEventLogToWorkerThread((unixNanos, elapsedNanos, realtimeNanos) -> {
EventLogTags.writeJankCujEventsCancelRequest(
cujType, unixNanos, elapsedNanos, realtimeNanos);
@@ -790,16 +434,18 @@
* @return boolean true if the tracker is cancelled successfully, false otherwise.
*/
@VisibleForTesting
- public boolean cancel(@CujType int cujType, @Reasons int reason) {
- FrameTracker tracker = getTracker(cujType);
+ public boolean cancel(@Cuj.CujType int cujType, @Reasons int reason) {
+ RunningTracker tracker = getTracker(cujType);
// Skip this call since we haven't started a trace yet.
- if (tracker == null) return false;
+ if (tracker == null) {
+ return false;
+ }
try {
final TrackerResult result = new TrackerResult();
- final boolean success = tracker.getHandler().runWithScissors(
- () -> result.mResult = cancelInternal(cujType, reason), EXECUTOR_TASK_TIMEOUT);
+ final boolean success = tracker.mConfig.getHandler().runWithScissors(
+ () -> result.mResult = cancelInternal(tracker, reason), EXECUTOR_TASK_TIMEOUT);
if (!success) {
- Log.d(TAG, "cancel failed due to timeout, CUJ=" + getNameOfCuj(cujType));
+ Log.d(TAG, "cancel failed due to timeout, CUJ=" + Cuj.getNameOfCuj(cujType));
return false;
}
return result.mResult;
@@ -810,71 +456,86 @@
}
@UiThread
- private boolean cancelInternal(@CujType int cujType, @Reasons int reason) {
- // remove the timeout action first.
- removeTimeout(cujType);
- FrameTracker tracker = getTracker(cujType);
- if (tracker == null) return false;
- // if the cancel call doesn't return true, another thread is handling cancel of the cuj.
- if (tracker.cancel(reason)) {
- removeTracker(cujType, reason);
+ private boolean cancelInternal(RunningTracker tracker, @Reasons int reason) {
+ if (removeTrackerIfCurrent(tracker, reason)) {
+ return false;
}
+ tracker.mTracker.cancel(reason);
return true;
}
@UiThread
- private void putTracker(@CujType int cuj, @NonNull FrameTracker tracker) {
+ private RunningTracker putTrackerIfNoCurrent(
+ @Cuj.CujType int cuj, Supplier<RunningTracker> supplier) {
synchronized (mLock) {
+ if (mRunningTrackers.contains(cuj)) {
+ return null;
+ }
+
+ RunningTracker tracker = supplier.get();
+ if (tracker == null) {
+ return null;
+ }
+
mRunningTrackers.put(cuj, tracker);
if (mDebugOverlay != null) {
mDebugOverlay.onTrackerAdded(cuj, tracker);
}
- if (DEBUG) {
- Log.d(TAG, "Added tracker for " + getNameOfCuj(cuj)
- + ". mRunningTrackers=" + listNamesOfCujs(mRunningTrackers));
- }
+
+ return tracker;
}
}
- private FrameTracker getTracker(@CujType int cuj) {
+ private RunningTracker getTracker(@Cuj.CujType int cuj) {
synchronized (mLock) {
return mRunningTrackers.get(cuj);
}
}
+ /**
+ * @return {@code true} if another tracker is current
+ */
@UiThread
- private void removeTracker(@CujType int cuj, int reason) {
+ private boolean removeTrackerIfCurrent(RunningTracker tracker, int reason) {
+ return removeTrackerIfCurrent(tracker.mConfig.mCujType, tracker.mTracker, reason);
+ }
+
+ /**
+ * @return {@code true} if another tracker is current
+ */
+ @UiThread
+ private boolean removeTrackerIfCurrent(@Cuj.CujType int cuj, FrameTracker tracker, int reason) {
synchronized (mLock) {
+ RunningTracker running = mRunningTrackers.get(cuj);
+ if (running == null || running.mTracker != tracker) {
+ return true;
+ }
+
+ running.mConfig.getHandler().removeCallbacks(running.mTimeoutAction);
mRunningTrackers.remove(cuj);
if (mDebugOverlay != null) {
mDebugOverlay.onTrackerRemoved(cuj, reason, mRunningTrackers);
}
- if (DEBUG) {
- Log.d(TAG, "Removed tracker for " + getNameOfCuj(cuj)
- + ". mRunningTrackers=" + listNamesOfCujs(mRunningTrackers));
- }
+ return false;
}
}
@WorkerThread
- private void updateProperties(DeviceConfig.Properties properties) {
+ @VisibleForTesting
+ public void updateProperties(DeviceConfig.Properties properties) {
for (String property : properties.getKeyset()) {
switch (property) {
- case SETTINGS_SAMPLING_INTERVAL_KEY:
- mSamplingInterval = properties.getInt(property, DEFAULT_SAMPLING_INTERVAL);
- break;
- case SETTINGS_THRESHOLD_MISSED_FRAMES_KEY:
- mTraceThresholdMissedFrames =
- properties.getInt(property, DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES);
- break;
- case SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY:
- mTraceThresholdFrameTimeMillis =
- properties.getInt(property, DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS);
- break;
- case SETTINGS_ENABLED_KEY:
- mEnabled = properties.getBoolean(property, DEFAULT_ENABLED);
- break;
- case SETTINGS_DEBUG_OVERLAY_ENABLED_KEY:
+ case SETTINGS_SAMPLING_INTERVAL_KEY ->
+ mSamplingInterval = properties.getInt(property, DEFAULT_SAMPLING_INTERVAL);
+ case SETTINGS_THRESHOLD_MISSED_FRAMES_KEY ->
+ mTraceThresholdMissedFrames =
+ properties.getInt(property, DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES);
+ case SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY ->
+ mTraceThresholdFrameTimeMillis =
+ properties.getInt(property, DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS);
+ case SETTINGS_ENABLED_KEY ->
+ mEnabled = properties.getBoolean(property, DEFAULT_ENABLED);
+ case SETTINGS_DEBUG_OVERLAY_ENABLED_KEY -> {
// Never allow the debug overlay to be used on user builds
boolean debugOverlayEnabled = Build.IS_DEBUGGABLE
&& properties.getBoolean(property, DEFAULT_DEBUG_OVERLAY_ENABLED);
@@ -885,49 +546,35 @@
mDebugOverlay.dispose();
mDebugOverlay = null;
}
- break;
- default:
- if (DEBUG) {
- Log.d(TAG, "Got a change event for an unknown property: "
- + property + " => " + properties.getString(property, ""));
- }
+ }
+ default -> Log.w(TAG, "Got a change event for an unknown property: "
+ + property + " => " + properties.getString(property, ""));
}
}
}
- @VisibleForTesting
- public DeviceConfig.OnPropertiesChangedListener getPropertiesChangedListener() {
- return mPropertiesChangedListener;
- }
-
- /**
- * Triggers the perfetto daemon to collect and upload data.
- */
- @VisibleForTesting
- public void trigger(Session session) {
- mWorker.getThreadHandler().post(
- () -> PerfettoTrigger.trigger(session.getPerfettoTrigger()));
- }
-
/**
* A helper method to translate interaction type to CUJ name.
*
* @param interactionType the interaction type defined in AtomsProto.java
* @return the name of the interaction type
+ * @deprecated use {@link Cuj#getNameOfInteraction(int)}
*/
+ @Deprecated
public static String getNameOfInteraction(int interactionType) {
- // There is an offset amount of 1 between cujType and interactionType.
- return getNameOfCuj(getCujTypeFromInteraction(interactionType));
+ return Cuj.getNameOfInteraction(interactionType);
}
/**
- * A helper method to translate interaction type to CUJ type.
+ * A helper method to translate CUJ type to CUJ name.
*
- * @param interactionType the interaction type defined in AtomsProto.java
- * @return the integer in {@link CujType}
+ * @param cujType the cuj type defined in this file
+ * @return the name of the cuj type
+ * @deprecated use {@link Cuj#getNameOfCuj(int)}
*/
- private static int getCujTypeFromInteraction(int interactionType) {
- return interactionType - 1;
+ @Deprecated
+ public static String getNameOfCuj(int cujType) {
+ return Cuj.getNameOfCuj(cujType);
}
/**
@@ -943,195 +590,14 @@
mDebugYOffset = yOffset;
}
- /**
- * A helper method for getting a string representation of all running CUJs. For example,
- * "(LOCKSCREEN_TRANSITION_FROM_AOD, IME_INSETS_ANIMATION)"
- */
- private static String listNamesOfCujs(SparseArray<FrameTracker> trackers) {
- if (!DEBUG) {
- return null;
- }
- StringBuilder sb = new StringBuilder();
- sb.append('(');
- for (int i = 0; i < trackers.size(); i++) {
- sb.append(getNameOfCuj(trackers.keyAt(i)));
- if (i < trackers.size() - 1) {
- sb.append(", ");
- }
- }
- sb.append(')');
- return sb.toString();
- }
+ private void postEventLogToWorkerThread(TimeFunction logFunction) {
+ final Instant now = Instant.now();
+ final long unixNanos = TimeUnit.NANOSECONDS.convert(now.getEpochSecond(), TimeUnit.SECONDS)
+ + now.getNano();
+ final long elapsedNanos = SystemClock.elapsedRealtimeNanos();
+ final long realtimeNanos = SystemClock.uptimeNanos();
- /**
- * A helper method to translate CUJ type to CUJ name.
- *
- * @param cujType the cuj type defined in this file
- * @return the name of the cuj type
- */
- public static String getNameOfCuj(int cujType) {
- // Please note:
- // 1. The length of the returned string shouldn't exceed MAX_LENGTH_OF_CUJ_NAME.
- // 2. The returned string should be the same with the name defined in atoms.proto.
- switch (cujType) {
- case CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE:
- return "NOTIFICATION_SHADE_EXPAND_COLLAPSE";
- case CUJ_NOTIFICATION_SHADE_SCROLL_FLING:
- return "NOTIFICATION_SHADE_SCROLL_FLING";
- case CUJ_NOTIFICATION_SHADE_ROW_EXPAND:
- return "NOTIFICATION_SHADE_ROW_EXPAND";
- case CUJ_NOTIFICATION_SHADE_ROW_SWIPE:
- return "NOTIFICATION_SHADE_ROW_SWIPE";
- case CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE:
- return "NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE";
- case CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE:
- return "NOTIFICATION_SHADE_QS_SCROLL_SWIPE";
- case CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS:
- return "LAUNCHER_APP_LAUNCH_FROM_RECENTS";
- case CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON:
- return "LAUNCHER_APP_LAUNCH_FROM_ICON";
- case CUJ_LAUNCHER_APP_CLOSE_TO_HOME:
- return "LAUNCHER_APP_CLOSE_TO_HOME";
- case CUJ_LAUNCHER_APP_CLOSE_TO_PIP:
- return "LAUNCHER_APP_CLOSE_TO_PIP";
- case CUJ_LAUNCHER_QUICK_SWITCH:
- return "LAUNCHER_QUICK_SWITCH";
- case CUJ_NOTIFICATION_HEADS_UP_APPEAR:
- return "NOTIFICATION_HEADS_UP_APPEAR";
- case CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR:
- return "NOTIFICATION_HEADS_UP_DISAPPEAR";
- case CUJ_NOTIFICATION_ADD:
- return "NOTIFICATION_ADD";
- case CUJ_NOTIFICATION_REMOVE:
- return "NOTIFICATION_REMOVE";
- case CUJ_NOTIFICATION_APP_START:
- return "NOTIFICATION_APP_START";
- case CUJ_LOCKSCREEN_PASSWORD_APPEAR:
- return "LOCKSCREEN_PASSWORD_APPEAR";
- case CUJ_LOCKSCREEN_PATTERN_APPEAR:
- return "LOCKSCREEN_PATTERN_APPEAR";
- case CUJ_LOCKSCREEN_PIN_APPEAR:
- return "LOCKSCREEN_PIN_APPEAR";
- case CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR:
- return "LOCKSCREEN_PASSWORD_DISAPPEAR";
- case CUJ_LOCKSCREEN_PATTERN_DISAPPEAR:
- return "LOCKSCREEN_PATTERN_DISAPPEAR";
- case CUJ_LOCKSCREEN_PIN_DISAPPEAR:
- return "LOCKSCREEN_PIN_DISAPPEAR";
- case CUJ_LOCKSCREEN_TRANSITION_FROM_AOD:
- return "LOCKSCREEN_TRANSITION_FROM_AOD";
- case CUJ_LOCKSCREEN_TRANSITION_TO_AOD:
- return "LOCKSCREEN_TRANSITION_TO_AOD";
- case CUJ_LAUNCHER_OPEN_ALL_APPS :
- return "LAUNCHER_OPEN_ALL_APPS";
- case CUJ_LAUNCHER_ALL_APPS_SCROLL:
- return "LAUNCHER_ALL_APPS_SCROLL";
- case CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET:
- return "LAUNCHER_APP_LAUNCH_FROM_WIDGET";
- case CUJ_SETTINGS_PAGE_SCROLL:
- return "SETTINGS_PAGE_SCROLL";
- case CUJ_LOCKSCREEN_UNLOCK_ANIMATION:
- return "LOCKSCREEN_UNLOCK_ANIMATION";
- case CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON:
- return "SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON";
- case CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER:
- return "SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER";
- case CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE:
- return "SHADE_APP_LAUNCH_FROM_QS_TILE";
- case CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON:
- return "SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON";
- case CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP:
- return "STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP";
- case CUJ_PIP_TRANSITION:
- return "PIP_TRANSITION";
- case CUJ_WALLPAPER_TRANSITION:
- return "WALLPAPER_TRANSITION";
- case CUJ_USER_SWITCH:
- return "USER_SWITCH";
- case CUJ_SPLASHSCREEN_AVD:
- return "SPLASHSCREEN_AVD";
- case CUJ_SPLASHSCREEN_EXIT_ANIM:
- return "SPLASHSCREEN_EXIT_ANIM";
- case CUJ_SCREEN_OFF:
- return "SCREEN_OFF";
- case CUJ_SCREEN_OFF_SHOW_AOD:
- return "SCREEN_OFF_SHOW_AOD";
- case CUJ_ONE_HANDED_ENTER_TRANSITION:
- return "ONE_HANDED_ENTER_TRANSITION";
- case CUJ_ONE_HANDED_EXIT_TRANSITION:
- return "ONE_HANDED_EXIT_TRANSITION";
- case CUJ_UNFOLD_ANIM:
- return "UNFOLD_ANIM";
- case CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS:
- return "SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS";
- case CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS:
- return "SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS";
- case CUJ_SUW_LOADING_TO_NEXT_FLOW:
- return "SUW_LOADING_TO_NEXT_FLOW";
- case CUJ_SUW_LOADING_SCREEN_FOR_STATUS:
- return "SUW_LOADING_SCREEN_FOR_STATUS";
- case CUJ_SPLIT_SCREEN_ENTER:
- return "SPLIT_SCREEN_ENTER";
- case CUJ_SPLIT_SCREEN_EXIT:
- return "SPLIT_SCREEN_EXIT";
- case CUJ_LOCKSCREEN_LAUNCH_CAMERA:
- return "LOCKSCREEN_LAUNCH_CAMERA";
- case CUJ_SPLIT_SCREEN_RESIZE:
- return "SPLIT_SCREEN_RESIZE";
- case CUJ_SETTINGS_SLIDER:
- return "SETTINGS_SLIDER";
- case CUJ_TAKE_SCREENSHOT:
- return "TAKE_SCREENSHOT";
- case CUJ_VOLUME_CONTROL:
- return "VOLUME_CONTROL";
- case CUJ_BIOMETRIC_PROMPT_TRANSITION:
- return "BIOMETRIC_PROMPT_TRANSITION";
- case CUJ_SETTINGS_TOGGLE:
- return "SETTINGS_TOGGLE";
- case CUJ_SHADE_DIALOG_OPEN:
- return "SHADE_DIALOG_OPEN";
- case CUJ_USER_DIALOG_OPEN:
- return "USER_DIALOG_OPEN";
- case CUJ_TASKBAR_EXPAND:
- return "TASKBAR_EXPAND";
- case CUJ_TASKBAR_COLLAPSE:
- return "TASKBAR_COLLAPSE";
- case CUJ_SHADE_CLEAR_ALL:
- return "SHADE_CLEAR_ALL";
- case CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION:
- return "LAUNCHER_UNLOCK_ENTRANCE_ANIMATION";
- case CUJ_LOCKSCREEN_OCCLUSION:
- return "LOCKSCREEN_OCCLUSION";
- case CUJ_RECENTS_SCROLLING:
- return "RECENTS_SCROLLING";
- case CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS:
- return "LAUNCHER_APP_SWIPE_TO_RECENTS";
- case CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE:
- return "LAUNCHER_CLOSE_ALL_APPS_SWIPE";
- case CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME:
- return "LAUNCHER_CLOSE_ALL_APPS_TO_HOME";
- case CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION:
- return "LOCKSCREEN_CLOCK_MOVE_ANIMATION";
- case CUJ_LAUNCHER_OPEN_SEARCH_RESULT:
- return "LAUNCHER_OPEN_SEARCH_RESULT";
- case CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK:
- return "LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK";
- case CUJ_IME_INSETS_SHOW_ANIMATION:
- return "IME_INSETS_SHOW_ANIMATION";
- case CUJ_IME_INSETS_HIDE_ANIMATION:
- return "IME_INSETS_HIDE_ANIMATION";
- case CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER:
- return "SPLIT_SCREEN_DOUBLE_TAP_DIVIDER";
- case CUJ_LAUNCHER_UNFOLD_ANIM:
- return "LAUNCHER_UNFOLD_ANIM";
- case CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY:
- return "PREDICTIVE_BACK_CROSS_ACTIVITY";
- case CUJ_PREDICTIVE_BACK_CROSS_TASK:
- return "PREDICTIVE_BACK_CROSS_TASK";
- case CUJ_PREDICTIVE_BACK_HOME:
- return "PREDICTIVE_BACK_HOME";
- }
- return "UNKNOWN";
+ mWorker.post(() -> logFunction.invoke(unixNanos, elapsedNanos, realtimeNanos));
}
private static class TrackerResult {
@@ -1147,9 +613,10 @@
private final Context mContext;
private final long mTimeout;
private final String mTag;
+ private final String mSessionName;
private final boolean mSurfaceOnly;
private final SurfaceControl mSurfaceControl;
- private final @CujType int mCujType;
+ private final @Cuj.CujType int mCujType;
private final boolean mDeferMonitor;
private final Handler mHandler;
@@ -1167,17 +634,17 @@
private String mAttrTag = "";
private boolean mAttrSurfaceOnly;
private SurfaceControl mAttrSurfaceControl;
- private @CujType int mAttrCujType;
+ private final @Cuj.CujType int mAttrCujType;
private boolean mAttrDeferMonitor = true;
/**
* Creates a builder which instruments only surface.
- * @param cuj The enum defined in {@link InteractionJankMonitor.CujType}.
+ * @param cuj The enum defined in {@link Cuj.CujType}.
* @param context context
* @param surfaceControl surface control
* @return builder
*/
- public static Builder withSurface(@CujType int cuj, @NonNull Context context,
+ public static Builder withSurface(@Cuj.CujType int cuj, @NonNull Context context,
@NonNull SurfaceControl surfaceControl) {
return new Builder(cuj)
.setContext(context)
@@ -1187,16 +654,17 @@
/**
* Creates a builder which instruments both surface and view.
- * @param cuj The enum defined in {@link InteractionJankMonitor.CujType}.
+ * @param cuj The enum defined in {@link Cuj.CujType}.
* @param view view
* @return builder
*/
- public static Builder withView(@CujType int cuj, @NonNull View view) {
- return new Builder(cuj).setView(view)
+ public static Builder withView(@Cuj.CujType int cuj, @NonNull View view) {
+ return new Builder(cuj)
+ .setView(view)
.setContext(view.getContext());
}
- private Builder(@CujType int cuj) {
+ private Builder(@Cuj.CujType int cuj) {
mAttrCujType = cuj;
}
@@ -1281,11 +749,12 @@
}
}
- private Configuration(@CujType int cuj, View view, String tag, long timeout,
+ private Configuration(@Cuj.CujType int cuj, View view, @NonNull String tag, long timeout,
boolean surfaceOnly, Context context, SurfaceControl surfaceControl,
boolean deferMonitor) {
mCujType = cuj;
mTag = tag;
+ mSessionName = generateSessionName(Cuj.getNameOfCuj(cuj), tag);
mTimeout = timeout;
mView = view;
mSurfaceOnly = surfaceOnly;
@@ -1298,6 +767,23 @@
mHandler = mSurfaceOnly ? mContext.getMainThreadHandler() : mView.getHandler();
}
+ @VisibleForTesting
+ public static String generateSessionName(
+ @NonNull String cujName, @NonNull String cujPostfix) {
+ final boolean hasPostfix = !TextUtils.isEmpty(cujPostfix);
+ if (hasPostfix) {
+ final int remaining = MAX_LENGTH_SESSION_NAME - cujName.length();
+ if (cujPostfix.length() > remaining) {
+ cujPostfix = cujPostfix.substring(0, remaining - 3).concat("...");
+ }
+ }
+ // The max length of the whole string should be:
+ // 105 with postfix, 83 without postfix
+ return hasPostfix
+ ? TextUtils.formatSimple("J<%s::%s>", cujName, cujPostfix)
+ : TextUtils.formatSimple("J<%s>", cujName);
+ }
+
private void validate() {
boolean shouldThrow = false;
final StringBuilder msg = new StringBuilder();
@@ -1360,10 +846,10 @@
return mSurfaceControl;
}
- @VisibleForTesting
/**
* @return a view which is attached to the view tree.
*/
+ @VisibleForTesting
public View getView() {
return mView;
}
@@ -1375,7 +861,7 @@
return mDeferMonitor;
}
- @VisibleForTesting
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public Handler getHandler() {
return mHandler;
}
@@ -1387,79 +873,27 @@
public int getDisplayId() {
return (mSurfaceOnly ? mContext : mView.getContext()).getDisplayId();
}
- }
- /**
- * A class to represent a session.
- */
- public static class Session {
- @CujType
- private final int mCujType;
- private final long mTimeStamp;
- @Reasons
- private int mReason = REASON_END_UNKNOWN;
- private final String mName;
-
- public Session(@CujType int cujType, @NonNull String postfix) {
- mCujType = cujType;
- mTimeStamp = System.nanoTime();
- mName = generateSessionName(getNameOfCuj(cujType), postfix);
- }
-
- private String generateSessionName(@NonNull String cujName, @NonNull String cujPostfix) {
- final boolean hasPostfix = !TextUtils.isEmpty(cujPostfix);
- // We assert that the cujName shouldn't exceed MAX_LENGTH_OF_CUJ_NAME.
- if (cujName.length() > MAX_LENGTH_OF_CUJ_NAME) {
- throw new IllegalArgumentException(TextUtils.formatSimple(
- "The length of cuj name <%s> exceeds %d", cujName, MAX_LENGTH_OF_CUJ_NAME));
- }
- if (hasPostfix) {
- final int remaining = MAX_LENGTH_SESSION_NAME - cujName.length();
- if (cujPostfix.length() > remaining) {
- cujPostfix = cujPostfix.substring(0, remaining - 3).concat("...");
- }
- }
- // The max length of the whole string should be:
- // 105 with postfix, 83 without postfix
- return hasPostfix
- ? TextUtils.formatSimple("J<%s::%s>", cujName, cujPostfix)
- : TextUtils.formatSimple("J<%s>", cujName);
- }
-
- @CujType
- public int getCuj() {
- return mCujType;
+ public String getSessionName() {
+ return mSessionName;
}
public int getStatsdInteractionType() {
- return CUJ_TO_STATSD_INTERACTION_TYPE[mCujType];
+ return Cuj.getStatsdInteractionType(mCujType);
}
/** Describes whether the measurement from this session should be written to statsd. */
public boolean logToStatsd() {
- return getStatsdInteractionType() != NO_STATSD_LOGGING;
+ return Cuj.logToStatsd(mCujType);
}
public String getPerfettoTrigger() {
- return String.format(Locale.US, "com.android.telemetry.interaction-jank-monitor-%d",
- mCujType);
+ return TextUtils.formatSimple(
+ "com.android.telemetry.interaction-jank-monitor-%d", mCujType);
}
- public String getName() {
- return mName;
- }
-
- public long getTimeStamp() {
- return mTimeStamp;
- }
-
- public void setReason(@Reasons int reason) {
- mReason = reason;
- }
-
- @Reasons
- public int getReason() {
- return mReason;
+ public @Cuj.CujType int getCujType() {
+ return mCujType;
}
}
@@ -1468,15 +902,15 @@
void invoke(long unixNanos, long elapsedNanos, long realtimeNanos);
}
- private void postEventLogToWorkerThread(TimeFunction logFunction) {
- final Instant now = Instant.now();
- final long unixNanos = TimeUnit.NANOSECONDS.convert(now.getEpochSecond(), TimeUnit.SECONDS)
- + now.getNano();
- final long elapsedNanos = SystemClock.elapsedRealtimeNanos();
- final long realtimeNanos = SystemClock.uptimeNanos();
+ static class RunningTracker {
+ public final Configuration mConfig;
+ public final FrameTracker mTracker;
+ public final Runnable mTimeoutAction;
- mWorker.getThreadHandler().post(() -> {
- logFunction.invoke(unixNanos, elapsedNanos, realtimeNanos);
- });
+ RunningTracker(Configuration config, FrameTracker tracker, Runnable timeoutAction) {
+ this.mConfig = config;
+ this.mTracker = tracker;
+ this.mTimeoutAction = timeoutAction;
+ }
}
}
diff --git a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
index ef7944c..f3f16a0 100644
--- a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
+++ b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
@@ -34,7 +34,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.jank.FrameTracker.Reasons;
-import com.android.internal.jank.InteractionJankMonitor.CujType;
/**
* An overlay that uses WindowCallbacks to draw the names of all running CUJs to the window
@@ -94,14 +93,14 @@
}
@UiThread
- private boolean attachViewRootIfNeeded(FrameTracker tracker) {
- FrameTracker.ViewRootWrapper viewRoot = tracker.getViewRoot();
+ private boolean attachViewRootIfNeeded(InteractionJankMonitor.RunningTracker tracker) {
+ FrameTracker.ViewRootWrapper viewRoot = tracker.mTracker.getViewRoot();
if (mViewRoot == null && viewRoot != null) {
// Add a trace marker so we can identify traces that were captured while the debug
// overlay was enabled. Traces that use the debug overlay should NOT be used for
// performance analysis.
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, TRACK_NAME, "DEBUG_OVERLAY_DRAW", 0);
- mHandler = tracker.getHandler();
+ mHandler = tracker.mConfig.getHandler();
mViewRoot = viewRoot;
mHandler.runWithScissors(() -> viewRoot.addWindowCallbacks(this),
InteractionJankMonitor.EXECUTOR_TASK_TIMEOUT);
@@ -111,11 +110,12 @@
return false;
}
+ @GuardedBy("mLock")
private float getWidthOfLongestCujName(int cujFontSize) {
mDebugPaint.setTextSize(cujFontSize);
float maxLength = 0;
for (int i = 0; i < mRunningCujs.size(); i++) {
- String cujName = InteractionJankMonitor.getNameOfCuj(mRunningCujs.keyAt(i));
+ String cujName = Cuj.getNameOfCuj(mRunningCujs.keyAt(i));
float textLength = mDebugPaint.measureText(cujName);
if (textLength > maxLength) {
maxLength = textLength;
@@ -149,8 +149,8 @@
}
@UiThread
- void onTrackerRemoved(@CujType int removedCuj, @Reasons int reason,
- SparseArray<FrameTracker> runningTrackers) {
+ void onTrackerRemoved(@Cuj.CujType int removedCuj, @Reasons int reason,
+ SparseArray<InteractionJankMonitor.RunningTracker> runningTrackers) {
synchronized (mLock) {
mRunningCujs.put(removedCuj, reason);
// If REASON_STILL_RUNNING is not in mRunningCujs, then all CUJs have ended
@@ -164,7 +164,7 @@
// trackers
for (int i = 0; i < runningTrackers.size(); i++) {
if (mViewRoot.equals(
- runningTrackers.valueAt(i).getViewRoot())) {
+ runningTrackers.valueAt(i).mTracker.getViewRoot())) {
needsNewViewRoot = false;
break;
}
@@ -185,7 +185,7 @@
}
@UiThread
- void onTrackerAdded(@CujType int addedCuj, FrameTracker tracker) {
+ void onTrackerAdded(@Cuj.CujType int addedCuj, InteractionJankMonitor.RunningTracker tracker) {
synchronized (mLock) {
// Use REASON_STILL_RUNNING (not technically one of the '@Reasons') to indicate the CUJ
// is still running
@@ -230,41 +230,44 @@
int cujFontSize = dipToPx(18);
final float cujNameTextHeight = getTextHeight(cujFontSize);
final float packageNameTextHeight = getTextHeight(packageNameFontSize);
- float maxLength = getWidthOfLongestCujName(cujFontSize);
- final int dx = (int) ((w - maxLength) / 2f);
- canvas.translate(dx, dy);
- // Draw background rectangle for displaying the text showing the CUJ name
- mDebugPaint.setColor(mBgColor);
- canvas.drawRect(
- -padding * 2, // more padding on top so we can draw the package name
- -padding,
- padding * 2 + maxLength,
- padding * 2 + packageNameTextHeight + cujNameTextHeight * mRunningCujs.size(),
- mDebugPaint);
- mDebugPaint.setTextSize(packageNameFontSize);
- mDebugPaint.setColor(Color.BLACK);
- mDebugPaint.setStrikeThruText(false);
- canvas.translate(0, packageNameTextHeight);
- canvas.drawText("package:" + mPackageName, 0, 0, mDebugPaint);
- mDebugPaint.setTextSize(cujFontSize);
- // Draw text for CUJ names
- for (int i = 0; i < mRunningCujs.size(); i++) {
- int status = mRunningCujs.valueAt(i);
- if (status == REASON_STILL_RUNNING) {
- mDebugPaint.setColor(Color.BLACK);
- mDebugPaint.setStrikeThruText(false);
- } else if (status == REASON_END_NORMAL) {
- mDebugPaint.setColor(Color.GRAY);
- mDebugPaint.setStrikeThruText(false);
- } else {
- // Cancelled, or otherwise ended for a bad reason
- mDebugPaint.setColor(Color.RED);
- mDebugPaint.setStrikeThruText(true);
+ synchronized (mLock) {
+ float maxLength = getWidthOfLongestCujName(cujFontSize);
+
+ final int dx = (int) ((w - maxLength) / 2f);
+ canvas.translate(dx, dy);
+ // Draw background rectangle for displaying the text showing the CUJ name
+ mDebugPaint.setColor(mBgColor);
+ canvas.drawRect(
+ -padding * 2, // more padding on top so we can draw the package name
+ -padding,
+ padding * 2 + maxLength,
+ padding * 2 + packageNameTextHeight + cujNameTextHeight * mRunningCujs.size(),
+ mDebugPaint);
+ mDebugPaint.setTextSize(packageNameFontSize);
+ mDebugPaint.setColor(Color.BLACK);
+ mDebugPaint.setStrikeThruText(false);
+ canvas.translate(0, packageNameTextHeight);
+ canvas.drawText("package:" + mPackageName, 0, 0, mDebugPaint);
+ mDebugPaint.setTextSize(cujFontSize);
+ // Draw text for CUJ names
+ for (int i = 0; i < mRunningCujs.size(); i++) {
+ int status = mRunningCujs.valueAt(i);
+ if (status == REASON_STILL_RUNNING) {
+ mDebugPaint.setColor(Color.BLACK);
+ mDebugPaint.setStrikeThruText(false);
+ } else if (status == REASON_END_NORMAL) {
+ mDebugPaint.setColor(Color.GRAY);
+ mDebugPaint.setStrikeThruText(false);
+ } else {
+ // Cancelled, or otherwise ended for a bad reason
+ mDebugPaint.setColor(Color.RED);
+ mDebugPaint.setStrikeThruText(true);
+ }
+ String cujName = Cuj.getNameOfCuj(mRunningCujs.keyAt(i));
+ canvas.translate(0, cujNameTextHeight);
+ canvas.drawText(cujName, 0, 0, mDebugPaint);
}
- String cujName = InteractionJankMonitor.getNameOfCuj(mRunningCujs.keyAt(i));
- canvas.translate(0, cujNameTextHeight);
- canvas.drawText(cujName, 0, 0, mDebugPaint);
}
}
}
diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
index 1f44b33..ed943cb 100644
--- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
@@ -55,15 +55,20 @@
*
* @hide
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass(
+ "com.android.hoststubgen.nativesubstitution.LongArrayMultiStateCounter_host")
public final class LongArrayMultiStateCounter implements Parcelable {
/**
* Container for a native equivalent of a long[].
*/
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
+ @android.ravenwood.annotation.RavenwoodNativeSubstitutionClass(
+ "com.android.hoststubgen.nativesubstitution"
+ + ".LongArrayMultiStateCounter_host$LongArrayContainer_host")
public static class LongArrayContainer {
- private static final NativeAllocationRegistry sRegistry =
- NativeAllocationRegistry.createMalloced(
- LongArrayContainer.class.getClassLoader(), native_getReleaseFunc());
+ private static NativeAllocationRegistry sRegistry;
// Visible to other objects in this package so that it can be passed to @CriticalNative
// methods.
@@ -73,9 +78,26 @@
public LongArrayContainer(int length) {
mLength = length;
mNativeObject = native_init(length);
+ registerNativeAllocation();
+ }
+
+ @android.ravenwood.annotation.RavenwoodReplace
+ private void registerNativeAllocation() {
+ if (sRegistry == null) {
+ synchronized (LongArrayMultiStateCounter.class) {
+ if (sRegistry == null) {
+ sRegistry = NativeAllocationRegistry.createMalloced(
+ LongArrayContainer.class.getClassLoader(), native_getReleaseFunc());
+ }
+ }
+ }
sRegistry.registerNativeAllocation(this, mNativeObject);
}
+ private void registerNativeAllocation$ravenwood() {
+ // No-op under ravenwood
+ }
+
/**
* Copies the supplied values into the underlying native array.
*/
@@ -124,19 +146,17 @@
private static native long native_getReleaseFunc();
@FastNative
- private native void native_setValues(long nativeObject, long[] array);
+ private static native void native_setValues(long nativeObject, long[] array);
@FastNative
- private native void native_getValues(long nativeObject, long[] array);
+ private static native void native_getValues(long nativeObject, long[] array);
@FastNative
- private native boolean native_combineValues(long nativeObject, long[] array,
+ private static native boolean native_combineValues(long nativeObject, long[] array,
int[] indexMap);
}
- private static final NativeAllocationRegistry sRegistry =
- NativeAllocationRegistry.createMalloced(
- LongArrayMultiStateCounter.class.getClassLoader(), native_getReleaseFunc());
+ private static volatile NativeAllocationRegistry sRegistry;
private static final AtomicReference<LongArrayContainer> sTmpArrayContainer =
new AtomicReference<>();
@@ -152,12 +172,30 @@
mStateCount = stateCount;
mLength = arrayLength;
mNativeObject = native_init(stateCount, arrayLength);
+ registerNativeAllocation();
+ }
+
+ @android.ravenwood.annotation.RavenwoodReplace
+ private void registerNativeAllocation() {
+ if (sRegistry == null) {
+ synchronized (LongArrayMultiStateCounter.class) {
+ if (sRegistry == null) {
+ sRegistry = NativeAllocationRegistry.createMalloced(
+ LongArrayMultiStateCounter.class.getClassLoader(),
+ native_getReleaseFunc());
+ }
+ }
+ }
sRegistry.registerNativeAllocation(this, mNativeObject);
}
+ private void registerNativeAllocation$ravenwood() {
+ // No-op under ravenwood
+ }
+
private LongArrayMultiStateCounter(Parcel in) {
mNativeObject = native_initFromParcel(in);
- sRegistry.registerNativeAllocation(this, mNativeObject);
+ registerNativeAllocation();
mStateCount = native_getStateCount(mNativeObject);
mLength = native_getArrayLength(mNativeObject);
@@ -361,10 +399,10 @@
long longArrayContainerNativeObject, int state);
@FastNative
- private native String native_toString(long nativeObject);
+ private static native String native_toString(long nativeObject);
@FastNative
- private native void native_writeToParcel(long nativeObject, Parcel dest, int flags);
+ private static native void native_writeToParcel(long nativeObject, Parcel dest, int flags);
@FastNative
private static native long native_initFromParcel(Parcel parcel);
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
index 4ed361f..6c09b7c 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
@@ -112,7 +112,7 @@
ParsingPackage addUsesOptionalNativeLibrary(String libraryName);
ParsingPackage addUsesSdkLibrary(String libraryName, long versionMajor,
- String[] certSha256Digests);
+ String[] certSha256Digests, boolean usesSdkLibrariesOptional);
ParsingPackage addUsesStaticLibrary(String libraryName, long version,
String[] certSha256Digests);
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index 4bb7c33..8c2a525 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -93,6 +93,7 @@
WM_DEBUG_DREAM(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM),
WM_DEBUG_DIMMER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
+ WM_DEBUG_TPL(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");
private final boolean mEnabled;
diff --git a/core/java/com/android/server/pm/pkg/AndroidPackage.java b/core/java/com/android/server/pm/pkg/AndroidPackage.java
index 4e4f26c..f86595f 100644
--- a/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -1340,6 +1340,15 @@
@Nullable
long[] getUsesSdkLibrariesVersionsMajor();
+
+ /**
+ * @see R.styleable#AndroidManifestUsesSdkLibrary_optional
+ * @hide
+ */
+ @Immutable.Ignore
+ @Nullable
+ boolean[] getUsesSdkLibrariesOptional();
+
/**
* TODO(b/135203078): Move static library stuff to an inner data class
*
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index f365dbb..2a744e3 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -49,8 +49,6 @@
"-Wno-unused-parameter",
"-Wunused",
"-Wunreachable-code",
-
- "-DNAMESPACE_FOR_HASH_FUNCTIONS=farmhash",
],
cppflags: ["-Wno-conversion-null"],
@@ -284,8 +282,6 @@
"libscrypt_static",
"libstatssocket_lazy",
"libskia",
- "libtextclassifier_hash_static",
- "libexpresslog_jni",
],
shared_libs: [
@@ -372,7 +368,6 @@
"bionic_libc_platform_headers",
"dnsproxyd_protocol_headers",
"flatbuffer_headers",
- "libtextclassifier_hash_headers",
"tensorflow_headers",
],
runtime_libs: [
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 50253cf..c24d21d 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -201,7 +201,6 @@
extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);
extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env);
extern int register_com_android_internal_content_om_OverlayManagerImpl(JNIEnv* env);
-extern int register_com_android_modules_expresslog_Utils(JNIEnv* env);
extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env);
extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
@@ -1590,7 +1589,6 @@
REG_JNI(register_android_os_incremental_IncrementalManager),
REG_JNI(register_com_android_internal_content_om_OverlayConfig),
REG_JNI(register_com_android_internal_content_om_OverlayManagerImpl),
- REG_JNI(register_com_android_modules_expresslog_Utils),
REG_JNI(register_com_android_internal_net_NetworkUtilsInternal),
REG_JNI(register_com_android_internal_os_ClassLoaderFactory),
REG_JNI(register_com_android_internal_os_LongArrayMultiStateCounter),
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index e0bcef6..de1ce4e 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -1014,6 +1014,10 @@
return cfg_state == CONFIG_SET;
}
+static jboolean android_os_Debug_logAllocatorStats(JNIEnv*, jobject) {
+ return mallopt(M_LOG_STATS, 0) == 1 ? JNI_TRUE : JNI_FALSE;
+}
+
/*
* JNI registration.
*/
@@ -1056,6 +1060,7 @@
{"getDmabufHeapPoolsSizeKb", "()J", (void*)android_os_Debug_getDmabufHeapPoolsSizeKb},
{"getGpuTotalUsageKb", "()J", (void*)android_os_Debug_getGpuTotalUsageKb},
{"isVmapStack", "()Z", (void*)android_os_Debug_isVmapStack},
+ {"logAllocatorStats", "()Z", (void*)android_os_Debug_logAllocatorStats},
};
int register_android_os_Debug(JNIEnv *env)
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 43e0c34..91dfc60 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -1238,7 +1238,7 @@
return killProcessGroup(uid, pid, SIGKILL);
}
-jboolean android_os_Process_sendSignalToProcessGroup(JNIEnv* env, jobject clazz, jint uid, jint pid,
+jint android_os_Process_sendSignalToProcessGroup(JNIEnv* env, jobject clazz, jint uid, jint pid,
jint signal) {
return sendSignalToProcessGroup(uid, pid, signal);
}
@@ -1310,7 +1310,7 @@
//{"setApplicationObject", "(Landroid/os/IBinder;)V",
//(void*)android_os_Process_setApplicationObject},
{"killProcessGroup", "(II)I", (void*)android_os_Process_killProcessGroup},
- {"sendSignalToProcessGroup", "(III)Z", (void*)android_os_Process_sendSignalToProcessGroup},
+ {"sendSignalToProcessGroup", "(III)I", (void*)android_os_Process_sendSignalToProcessGroup},
{"removeAllProcessGroups", "()V", (void*)android_os_Process_removeAllProcessGroups},
{"nativePidFdOpen", "(II)I", (void*)android_os_Process_nativePidFdOpen},
{"freezeCgroupUid", "(IZ)V", (void*)android_os_Process_freezeCgroupUID},
diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
index dab47e9..76b05ea 100644
--- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
@@ -107,7 +107,7 @@
*vector = counter->getCount(state);
}
-static jobject native_toString(JNIEnv *env, jobject self, jlong nativePtr) {
+static jobject native_toString(JNIEnv *env, jclass, jlong nativePtr) {
battery::LongArrayMultiStateCounter *counter =
reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
return env->NewStringUTF(counter->toString().c_str());
@@ -127,7 +127,7 @@
} \
}
-static void native_writeToParcel(JNIEnv *env, jobject self, jlong nativePtr, jobject jParcel,
+static void native_writeToParcel(JNIEnv *env, jclass, jlong nativePtr, jobject jParcel,
jint flags) {
battery::LongArrayMultiStateCounter *counter =
reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
@@ -161,7 +161,7 @@
} \
}
-static jlong native_initFromParcel(JNIEnv *env, jclass theClass, jobject jParcel) {
+static jlong native_initFromParcel(JNIEnv *env, jclass, jobject jParcel) {
ndk::ScopedAParcel parcel(AParcel_fromJavaParcel(env, jParcel));
int32_t stateCount;
@@ -253,7 +253,7 @@
return reinterpret_cast<jlong>(native_dispose_LongArrayContainer);
}
-static void native_setValues_LongArrayContainer(JNIEnv *env, jobject self, jlong nativePtr,
+static void native_setValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr,
jlongArray jarray) {
std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
ScopedLongArrayRO scopedArray(env, jarray);
@@ -264,7 +264,7 @@
std::copy(array, array + size, vector->data());
}
-static void native_getValues_LongArrayContainer(JNIEnv *env, jobject self, jlong nativePtr,
+static void native_getValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr,
jlongArray jarray) {
std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
ScopedLongArrayRW scopedArray(env, jarray);
@@ -273,7 +273,7 @@
std::copy(vector->data(), vector->data() + vector->size(), scopedArray.get());
}
-static jboolean native_combineValues_LongArrayContainer(JNIEnv *env, jobject self, jlong nativePtr,
+static jboolean native_combineValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr,
jlongArray jarray, jintArray jindexMap) {
std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
ScopedLongArrayRW scopedArray(env, jarray);
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index f8546b7..596cfe5 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2515,6 +2515,10 @@
<attr name="versionMajor" format="integer" />
<!-- The SHA-256 digest of the SDK library signing certificate. -->
<attr name="certDigest" format="string" />
+ <!-- Specify whether the SDK is optional. The default is false, false means app can be
+ installed even if the SDK library doesn't exist, and the SDK library can be uninstalled
+ when the app is still installed. -->
+ <attr name="optional" format="boolean" />
</declare-styleable>
<!-- The <code>static-library</code> tag declares that this apk is providing itself
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index b12f302..f10e7f8 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -115,6 +115,8 @@
<!-- @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime")
@hide @SystemApi -->
<public name="isVirtualDeviceOnly"/>
+ <!-- @FlaggedApi("android.content.pm.sdk_lib_independence") -->
+ <public name="optional"/>
</staging-public-group>
<staging-public-group type="id" first-id="0x01bc0000">
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 88abaa1..6706c91 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -185,6 +185,7 @@
"src/android/os/BuildTest.java",
"src/android/os/FileUtilsTest.java",
"src/android/util/**/*.java",
+ "src/com/android/internal/os/LongArrayMultiStateCounterTest.java",
"src/com/android/internal/util/**/*.java",
"testdoubles/src/com/android/internal/util/**/*.java",
],
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
index 7c4136d..9300d1e 100644
--- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -831,6 +831,7 @@
}
@Test
+ @FlakyTest(bugId = 299483542)
public void onPropertiesChangedListener_setPropertiesCallback() {
DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false);
DeviceConfig.setProperty(NAMESPACE, KEY2, VALUE2, false);
diff --git a/core/tests/coretests/src/com/android/internal/jank/CujTest.java b/core/tests/coretests/src/com/android/internal/jank/CujTest.java
new file mode 100644
index 0000000..bf35ed0
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/jank/CujTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.jank;
+
+import static android.text.TextUtils.formatSimple;
+
+import static com.android.internal.jank.Cuj.getNameOfCuj;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+@SmallTest
+public class CujTest {
+ private static final String ENUM_NAME_PREFIX =
+ "UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__";
+ private static final Set<String> DEPRECATED_VALUES = new HashSet<>() {
+ {
+ add(ENUM_NAME_PREFIX + "IME_INSETS_ANIMATION");
+ }
+ };
+ private static final Map<Integer, String> ENUM_NAME_EXCEPTION_MAP = new HashMap<>() {
+ {
+ put(Cuj.CUJ_NOTIFICATION_ADD, getEnumName("SHADE_NOTIFICATION_ADD"));
+ put(Cuj.CUJ_NOTIFICATION_HEADS_UP_APPEAR, getEnumName("SHADE_HEADS_UP_APPEAR"));
+ put(Cuj.CUJ_NOTIFICATION_APP_START, getEnumName("SHADE_APP_LAUNCH"));
+ put(Cuj.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR, getEnumName("SHADE_HEADS_UP_DISAPPEAR"));
+ put(Cuj.CUJ_NOTIFICATION_REMOVE, getEnumName("SHADE_NOTIFICATION_REMOVE"));
+ put(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, getEnumName("NOTIFICATION_SHADE_SWIPE"));
+ put(Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, getEnumName("SHADE_QS_EXPAND_COLLAPSE"));
+ put(Cuj.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE, getEnumName("SHADE_QS_SCROLL_SWIPE"));
+ put(Cuj.CUJ_NOTIFICATION_SHADE_ROW_EXPAND, getEnumName("SHADE_ROW_EXPAND"));
+ put(Cuj.CUJ_NOTIFICATION_SHADE_ROW_SWIPE, getEnumName("SHADE_ROW_SWIPE"));
+ put(Cuj.CUJ_NOTIFICATION_SHADE_SCROLL_FLING, getEnumName("SHADE_SCROLL_FLING"));
+ }
+ };
+
+ @Rule
+ public final Expect mExpect = Expect.create();
+
+ @Test
+ public void testCujNameLimit() {
+ getCujConstants().forEach(f -> {
+ final int cuj = getIntFieldChecked(f);
+ mExpect.withMessage(formatSimple("Too long CUJ(%d) name: %s", cuj, getNameOfCuj(cuj)))
+ .that(getNameOfCuj(cuj).length())
+ .isAtMost(Cuj.MAX_LENGTH_OF_CUJ_NAME);
+ });
+ }
+
+ @Test
+ public void testCujTypeEnumCorrectlyDefined() throws Exception {
+ List<Field> cujEnumFields = getCujConstants().toList();
+
+ HashSet<Integer> allValues = new HashSet<>();
+ for (Field field : cujEnumFields) {
+ int fieldValue = field.getInt(null);
+ assertWithMessage("All CujType values must be unique. Field %s repeats existing value.",
+ field.getName())
+ .that(allValues.add(fieldValue))
+ .isTrue();
+ assertWithMessage("Field %s must have a value <= LAST_CUJ", field.getName())
+ .that(fieldValue)
+ .isAtMost(Cuj.LAST_CUJ);
+ assertWithMessage("Field %s must have a statsd mapping.", field.getName())
+ .that(Cuj.logToStatsd(fieldValue))
+ .isTrue();
+ }
+ }
+
+ @Test
+ public void testCujsMapToEnumsCorrectly() {
+ List<Field> cujs = getCujConstants().toList();
+
+ Map<Integer, String> enumsMap = Arrays.stream(FrameworkStatsLog.class.getDeclaredFields())
+ .filter(f -> f.getName().startsWith(ENUM_NAME_PREFIX)
+ && !DEPRECATED_VALUES.contains(f.getName())
+ && Modifier.isStatic(f.getModifiers())
+ && f.getType() == int.class)
+ .collect(Collectors.toMap(CujTest::getIntFieldChecked, Field::getName));
+
+ assertThat(enumsMap.size() - 1).isEqualTo(cujs.size());
+
+ cujs.forEach(f -> {
+ final int cuj = getIntFieldChecked(f);
+ final String cujName = f.getName();
+ final String expectedEnumName =
+ ENUM_NAME_EXCEPTION_MAP.getOrDefault(cuj, getEnumName(cujName.substring(4)));
+ final int enumKey = Cuj.getStatsdInteractionType(cuj);
+ final String enumName = enumsMap.get(enumKey);
+ final String expectedNameOfCuj = formatSimple("CUJ_%s", getNameOfCuj(cuj));
+
+ mExpect.withMessage(
+ formatSimple("%s (%d) not matches %s (%d)", cujName, cuj, enumName, enumKey))
+ .that(expectedEnumName.equals(enumName))
+ .isTrue();
+ mExpect.withMessage(
+ formatSimple("getNameOfCuj(%d) not matches: %s, expected=%s",
+ cuj, cujName, expectedNameOfCuj))
+ .that(cujName.equals(expectedNameOfCuj))
+ .isTrue();
+ });
+ }
+
+ private static String getEnumName(String name) {
+ return formatSimple("%s%s", ENUM_NAME_PREFIX, name);
+ }
+
+ private static int getIntFieldChecked(Field field) {
+ try {
+ return field.getInt(null);
+ } catch (IllegalAccessException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private static Stream<Field> getCujConstants() {
+ return Arrays.stream(Cuj.class.getDeclaredFields())
+ .filter(f -> f.getName().startsWith("CUJ_")
+ && Modifier.isStatic(f.getModifiers())
+ && f.getType() == int.class);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
index 1b9717a..1a7117e 100644
--- a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
@@ -22,9 +22,8 @@
import static com.android.internal.jank.FrameTracker.SurfaceControlWrapper;
import static com.android.internal.jank.FrameTracker.ViewRootWrapper;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_TO_STATSD_INTERACTION_TYPE;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_WALLPAPER_TRANSITION;
+import static com.android.internal.jank.Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
+import static com.android.internal.jank.Cuj.CUJ_WALLPAPER_TRANSITION;
import static com.android.internal.util.FrameworkStatsLog.UI_INTERACTION_FRAME_INFO_REPORTED;
import static com.google.common.truth.Truth.assertThat;
@@ -49,15 +48,14 @@
import android.view.View;
import android.view.ViewAttachTestActivity;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.filters.SmallTest;
-import androidx.test.rule.ActivityTestRule;
import com.android.internal.jank.FrameTracker.ChoreographerWrapper;
import com.android.internal.jank.FrameTracker.FrameMetricsWrapper;
import com.android.internal.jank.FrameTracker.StatsLogWrapper;
import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper;
import com.android.internal.jank.InteractionJankMonitor.Configuration;
-import com.android.internal.jank.InteractionJankMonitor.Session;
import org.junit.Before;
import org.junit.Rule;
@@ -69,14 +67,14 @@
@SmallTest
public class FrameTrackerTest {
- private static final String CUJ_POSTFIX = "";
+ private static final String SESSION_NAME = "SessionName";
private static final long FRAME_TIME_60Hz = (long) 1e9 / 60;
private ViewAttachTestActivity mActivity;
@Rule
- public ActivityTestRule<ViewAttachTestActivity> mRule =
- new ActivityTestRule<>(ViewAttachTestActivity.class);
+ public ActivityScenarioRule<ViewAttachTestActivity> mRule =
+ new ActivityScenarioRule<>(ViewAttachTestActivity.class);
private ThreadedRendererWrapper mRenderer;
private FrameMetricsWrapper mWrapper;
@@ -86,12 +84,13 @@
private StatsLogWrapper mStatsLog;
private ArgumentCaptor<OnJankDataListener> mListenerCapture;
private SurfaceControl mSurfaceControl;
+ private FrameTracker.FrameTrackerListener mTrackerListener;
private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
@Before
public void setup() {
// Prepare an activity for getting ThreadedRenderer later.
- mActivity = mRule.getActivity();
+ mRule.getScenario().onActivity(activity -> mActivity = activity);
View view = mActivity.getWindow().getDecorView();
assertThat(view.isAttachedToWindow()).isTrue();
@@ -116,36 +115,38 @@
mChoreographer = mock(ChoreographerWrapper.class);
mStatsLog = mock(StatsLogWrapper.class);
mRunnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+ mTrackerListener = mock(FrameTracker.FrameTrackerListener.class);
}
- private FrameTracker spyFrameTracker(int cuj, String postfix, boolean surfaceOnly) {
- InteractionJankMonitor monitor = mock(InteractionJankMonitor.class);
- Handler handler = mRule.getActivity().getMainThreadHandler();
- Session session = new Session(cuj, postfix);
+ private FrameTracker spyFrameTracker(boolean surfaceOnly) {
+ Handler handler = mActivity.getMainThreadHandler();
Configuration config = mock(Configuration.class);
+ when(config.getSessionName()).thenReturn(SESSION_NAME);
when(config.isSurfaceOnly()).thenReturn(surfaceOnly);
when(config.getSurfaceControl()).thenReturn(mSurfaceControl);
when(config.shouldDeferMonitor()).thenReturn(true);
when(config.getDisplayId()).thenReturn(42);
- View view = mRule.getActivity().getWindow().getDecorView();
+ View view = mActivity.getWindow().getDecorView();
Handler spyHandler = spy(new Handler(handler.getLooper()));
when(config.getView()).thenReturn(surfaceOnly ? null : view);
when(config.getHandler()).thenReturn(spyHandler);
+ when(config.logToStatsd()).thenReturn(true);
+ when(config.getStatsdInteractionType()).thenReturn(surfaceOnly
+ ? Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)
+ : Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE));
FrameTracker frameTracker = Mockito.spy(
- new FrameTracker(monitor, session, spyHandler, mRenderer, mViewRootWrapper,
+ new FrameTracker(config, mRenderer, mViewRootWrapper,
mSurfaceControlWrapper, mChoreographer, mWrapper, mStatsLog,
/* traceThresholdMissedFrames= */ 1,
/* traceThresholdFrameTimeMillis= */ -1,
- /* FrameTrackerListener= */ null, config));
- doNothing().when(frameTracker).triggerPerfetto();
+ mTrackerListener));
doNothing().when(frameTracker).postTraceStartMarker(mRunnableArgumentCaptor.capture());
return frameTracker;
}
@Test
public void testOnlyFirstWindowFrameOverThreshold() {
- FrameTracker tracker = spyFrameTracker(
- CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+ FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
// Just provide current timestamp anytime mWrapper asked for VSYNC_TIMESTAMP
when(mWrapper.getMetric(FrameMetrics.VSYNC_TIMESTAMP))
@@ -169,11 +170,11 @@
sendFrame(tracker, 500, JANK_APP_DEADLINE_MISSED, 103L);
verify(tracker).removeObservers();
- verify(tracker, never()).triggerPerfetto();
+ verify(mTrackerListener, never()).triggerPerfetto(any());
verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
eq(42), /* displayId */
eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
- eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]),
+ eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)),
eq(2L) /* totalFrames */,
eq(0L) /* missedFrames */,
eq(5000000L) /* maxFrameTimeNanos */,
@@ -184,8 +185,7 @@
@Test
public void testSfJank() {
- FrameTracker tracker = spyFrameTracker(
- CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+ FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
@@ -206,12 +206,12 @@
verify(tracker).removeObservers();
// We detected a janky frame - trigger Perfetto
- verify(tracker).triggerPerfetto();
+ verify(mTrackerListener).triggerPerfetto(any());
verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
eq(42), /* displayId */
eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
- eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]),
+ eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)),
eq(2L) /* totalFrames */,
eq(1L) /* missedFrames */,
eq(40000000L) /* maxFrameTimeNanos */,
@@ -222,8 +222,7 @@
@Test
public void testFirstFrameJankyNoTrigger() {
- FrameTracker tracker = spyFrameTracker(
- CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+ FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
@@ -243,13 +242,12 @@
verify(tracker).removeObservers();
- // We detected a janky frame - trigger Perfetto
- verify(tracker, never()).triggerPerfetto();
+ verify(mTrackerListener, never()).triggerPerfetto(any());
verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
eq(42), /* displayId */
eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
- eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]),
+ eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)),
eq(2L) /* totalFrames */,
eq(0L) /* missedFrames */,
eq(4000000L) /* maxFrameTimeNanos */,
@@ -260,8 +258,7 @@
@Test
public void testOtherFrameOverThreshold() {
- FrameTracker tracker = spyFrameTracker(
- CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+ FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
@@ -282,12 +279,12 @@
verify(tracker).removeObservers();
// We detected a janky frame - trigger Perfetto
- verify(tracker).triggerPerfetto();
+ verify(mTrackerListener).triggerPerfetto(any());
verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
eq(42), /* displayId */
eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
- eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]),
+ eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)),
eq(2L) /* totalFrames */,
eq(1L) /* missedFrames */,
eq(40000000L) /* maxFrameTimeNanos */,
@@ -298,8 +295,7 @@
@Test
public void testLastFrameOverThresholdBeforeEnd() {
- FrameTracker tracker = spyFrameTracker(
- CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+ FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
@@ -323,12 +319,12 @@
verify(tracker).removeObservers();
// We detected a janky frame - trigger Perfetto
- verify(tracker).triggerPerfetto();
+ verify(mTrackerListener).triggerPerfetto(any());
verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
eq(42), /* displayId */
eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
- eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]),
+ eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)),
eq(2L) /* totalFrames */,
eq(1L) /* missedFrames */,
eq(50000000L) /* maxFrameTimeNanos */,
@@ -342,8 +338,7 @@
*/
@Test
public void testNoOvercountingAfterEnd() {
- FrameTracker tracker = spyFrameTracker(
- CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+ FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
@@ -367,11 +362,11 @@
sendFrame(tracker, 50, JANK_APP_DEADLINE_MISSED, 103L);
verify(tracker).removeObservers();
- verify(tracker, never()).triggerPerfetto();
+ verify(mTrackerListener, never()).triggerPerfetto(any());
verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
eq(42), /* displayId */
eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
- eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]),
+ eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)),
eq(2L) /* totalFrames */,
eq(0L) /* missedFrames */,
eq(4000000L) /* maxFrameTimeNanos */,
@@ -382,8 +377,7 @@
@Test
public void testBeginCancel() {
- FrameTracker tracker = spyFrameTracker(
- CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+ FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
@@ -402,13 +396,12 @@
tracker.cancel(FrameTracker.REASON_CANCEL_NORMAL);
verify(tracker).removeObservers();
// Since the tracker has been cancelled, shouldn't trigger perfetto.
- verify(tracker, never()).triggerPerfetto();
+ verify(mTrackerListener, never()).triggerPerfetto(any());
}
@Test
public void testCancelIfEndVsyncIdEqualsToBeginVsyncId() {
- FrameTracker tracker = spyFrameTracker(
- CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+ FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
@@ -426,13 +419,12 @@
verify(tracker).removeObservers();
// Should never trigger Perfetto since it is a cancel.
- verify(tracker, never()).triggerPerfetto();
+ verify(mTrackerListener, never()).triggerPerfetto(any());
}
@Test
public void testCancelIfEndVsyncIdLessThanBeginVsyncId() {
- FrameTracker tracker = spyFrameTracker(
- CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+ FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
@@ -450,13 +442,12 @@
verify(tracker).removeObservers();
// Should never trigger Perfetto since it is a cancel.
- verify(tracker, never()).triggerPerfetto();
+ verify(mTrackerListener, never()).triggerPerfetto(any());
}
@Test
public void testCancelWhenSessionNeverBegun() {
- FrameTracker tracker = spyFrameTracker(
- CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+ FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
tracker.cancel(FrameTracker.REASON_CANCEL_NORMAL);
verify(tracker).removeObservers();
@@ -464,8 +455,7 @@
@Test
public void testEndWhenSessionNeverBegun() {
- FrameTracker tracker = spyFrameTracker(
- CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+ FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
tracker.end(FrameTracker.REASON_END_NORMAL);
verify(tracker).removeObservers();
@@ -473,8 +463,7 @@
@Test
public void testSurfaceOnlyOtherFrameJanky() {
- FrameTracker tracker = spyFrameTracker(
- CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true);
+ FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ true);
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
@@ -495,12 +484,12 @@
sendFrame(tracker, JANK_NONE, 103L);
verify(mSurfaceControlWrapper).removeJankStatsListener(any());
- verify(tracker).triggerPerfetto();
+ verify(mTrackerListener).triggerPerfetto(any());
verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
eq(42), /* displayId */
eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
- eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]),
+ eq(Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)),
eq(2L) /* totalFrames */,
eq(1L) /* missedFrames */,
eq(0L) /* maxFrameTimeNanos */,
@@ -511,8 +500,7 @@
@Test
public void testSurfaceOnlyFirstFrameJanky() {
- FrameTracker tracker = spyFrameTracker(
- CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true);
+ FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ true);
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
@@ -533,12 +521,12 @@
sendFrame(tracker, JANK_NONE, 103L);
verify(mSurfaceControlWrapper).removeJankStatsListener(any());
- verify(tracker, never()).triggerPerfetto();
+ verify(mTrackerListener, never()).triggerPerfetto(any());
verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
eq(42), /* displayId */
eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
- eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]),
+ eq(Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)),
eq(2L) /* totalFrames */,
eq(0L) /* missedFrames */,
eq(0L) /* maxFrameTimeNanos */,
@@ -549,8 +537,7 @@
@Test
public void testSurfaceOnlyLastFrameJanky() {
- FrameTracker tracker = spyFrameTracker(
- CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true);
+ FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ true);
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
@@ -571,12 +558,12 @@
sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 103L);
verify(mSurfaceControlWrapper).removeJankStatsListener(any());
- verify(tracker, never()).triggerPerfetto();
+ verify(mTrackerListener, never()).triggerPerfetto(any());
verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
eq(42), /* displayId */
eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
- eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]),
+ eq(Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)),
eq(2L) /* totalFrames */,
eq(0L) /* missedFrames */,
eq(0L) /* maxFrameTimeNanos */,
@@ -587,8 +574,7 @@
@Test
public void testMaxSuccessiveMissedFramesCount() {
- FrameTracker tracker = spyFrameTracker(
- CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true);
+ FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ true);
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
mRunnableArgumentCaptor.getValue().run();
@@ -604,11 +590,11 @@
sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 106L);
sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 107L);
verify(mSurfaceControlWrapper).removeJankStatsListener(any());
- verify(tracker).triggerPerfetto();
+ verify(mTrackerListener).triggerPerfetto(any());
verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
eq(42), /* displayId */
eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
- eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]),
+ eq(Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)),
eq(6L) /* totalFrames */,
eq(5L) /* missedFrames */,
eq(0L) /* maxFrameTimeNanos */,
diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
index b61f995..68095e5 100644
--- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
@@ -20,23 +20,9 @@
import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT;
import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_ADD;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_APP_START;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_HEADS_UP_APPEAR;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_REMOVE;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_ROW_EXPAND;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_ROW_SWIPE;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_TO_STATSD_INTERACTION_TYPE;
-import static com.android.internal.jank.InteractionJankMonitor.MAX_LENGTH_OF_CUJ_NAME;
-import static com.android.internal.jank.InteractionJankMonitor.getNameOfCuj;
+import static com.android.internal.jank.InteractionJankMonitor.Configuration.generateSessionName;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -52,12 +38,11 @@
import android.os.HandlerThread;
import android.os.SystemClock;
import android.provider.DeviceConfig;
-import android.util.SparseArray;
import android.view.View;
import android.view.ViewAttachTestActivity;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.filters.SmallTest;
-import androidx.test.rule.ActivityTestRule;
import com.android.internal.jank.FrameTracker.ChoreographerWrapper;
import com.android.internal.jank.FrameTracker.FrameMetricsWrapper;
@@ -66,101 +51,54 @@
import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper;
import com.android.internal.jank.FrameTracker.ViewRootWrapper;
import com.android.internal.jank.InteractionJankMonitor.Configuration;
-import com.android.internal.jank.InteractionJankMonitor.Session;
-import com.android.internal.util.FrameworkStatsLog;
import com.google.common.truth.Expect;
import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
@SmallTest
public class InteractionJankMonitorTest {
- private static final String CUJ_POSTFIX = "";
- private static final SparseArray<String> ENUM_NAME_EXCEPTION_MAP = new SparseArray<>();
- private static final String ENUM_NAME_PREFIX =
- "UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__";
-
- private static final ArrayList<String> DEPRECATED_VALUES = new ArrayList<>();
-
private ViewAttachTestActivity mActivity;
private View mView;
+ private Handler mHandler;
private HandlerThread mWorker;
@Rule
- public ActivityTestRule<ViewAttachTestActivity> mRule =
- new ActivityTestRule<>(ViewAttachTestActivity.class);
+ public ActivityScenarioRule<ViewAttachTestActivity> mRule =
+ new ActivityScenarioRule<>(ViewAttachTestActivity.class);
@Rule
public final Expect mExpect = Expect.create();
- @BeforeClass
- public static void initialize() {
- ENUM_NAME_EXCEPTION_MAP.put(CUJ_NOTIFICATION_ADD, getEnumName("SHADE_NOTIFICATION_ADD"));
- ENUM_NAME_EXCEPTION_MAP.put(CUJ_NOTIFICATION_APP_START, getEnumName("SHADE_APP_LAUNCH"));
- ENUM_NAME_EXCEPTION_MAP.put(
- CUJ_NOTIFICATION_HEADS_UP_APPEAR, getEnumName("SHADE_HEADS_UP_APPEAR"));
- ENUM_NAME_EXCEPTION_MAP.put(
- CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR, getEnumName("SHADE_HEADS_UP_DISAPPEAR"));
- ENUM_NAME_EXCEPTION_MAP.put(
- CUJ_NOTIFICATION_REMOVE, getEnumName("SHADE_NOTIFICATION_REMOVE"));
- ENUM_NAME_EXCEPTION_MAP.put(
- CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, getEnumName("NOTIFICATION_SHADE_SWIPE"));
- ENUM_NAME_EXCEPTION_MAP.put(
- CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, getEnumName("SHADE_QS_EXPAND_COLLAPSE"));
- ENUM_NAME_EXCEPTION_MAP.put(
- CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE, getEnumName("SHADE_QS_SCROLL_SWIPE"));
- ENUM_NAME_EXCEPTION_MAP.put(
- CUJ_NOTIFICATION_SHADE_ROW_EXPAND, getEnumName("SHADE_ROW_EXPAND"));
- ENUM_NAME_EXCEPTION_MAP.put(
- CUJ_NOTIFICATION_SHADE_ROW_SWIPE, getEnumName("SHADE_ROW_SWIPE"));
- ENUM_NAME_EXCEPTION_MAP.put(
- CUJ_NOTIFICATION_SHADE_SCROLL_FLING, getEnumName("SHADE_SCROLL_FLING"));
- DEPRECATED_VALUES.add(ENUM_NAME_PREFIX + "IME_INSETS_ANIMATION");
- }
-
- private static String getEnumName(String name) {
- return formatSimple("%s%s", ENUM_NAME_PREFIX, name);
- }
-
@Before
public void setup() {
- // Prepare an activity for getting ThreadedRenderer later.
- mActivity = mRule.getActivity();
+ mRule.getScenario().onActivity(activity -> mActivity = activity);
mView = mActivity.getWindow().getDecorView();
assertThat(mView.isAttachedToWindow()).isTrue();
- Handler handler = spy(new Handler(mActivity.getMainLooper()));
- doReturn(true).when(handler).sendMessageAtTime(any(), anyLong());
+ mHandler = spy(new Handler(mActivity.getMainLooper()));
+ doReturn(true).when(mHandler).sendMessageAtTime(any(), anyLong());
mWorker = mock(HandlerThread.class);
- doReturn(handler).when(mWorker).getThreadHandler();
+ doReturn(mHandler).when(mWorker).getThreadHandler();
}
@Test
public void testBeginEnd() {
InteractionJankMonitor monitor = createMockedInteractionJankMonitor();
- FrameTracker tracker = createMockedFrameTracker(monitor, null);
- doReturn(tracker).when(monitor).createFrameTracker(any(), any());
+ FrameTracker tracker = createMockedFrameTracker();
+ doReturn(tracker).when(monitor).createFrameTracker(any());
doNothing().when(tracker).begin();
doReturn(true).when(tracker).end(anyInt());
// Simulate a trace session and see if begin / end are invoked.
- assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
+ assertThat(monitor.begin(mView, Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
verify(tracker).begin();
- assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
+ assertThat(monitor.end(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
verify(tracker).end(REASON_END_NORMAL);
}
@@ -172,10 +110,10 @@
propertiesValues.put("enabled", "false");
DeviceConfig.Properties properties = new DeviceConfig.Properties(
DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR, propertiesValues);
- monitor.getPropertiesChangedListener().onPropertiesChanged(properties);
+ monitor.updateProperties(properties);
- assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
- assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
+ assertThat(monitor.begin(mView, Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
+ assertThat(monitor.end(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
}
@Test
@@ -185,145 +123,57 @@
assertThat(view.isAttachedToWindow()).isFalse();
// Should return false if the view passed in is not attached to window yet.
- assertThat(monitor.begin(view, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
- assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
+ assertThat(monitor.begin(view, Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
+ assertThat(monitor.end(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
}
@Test
public void testBeginTimeout() {
ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
InteractionJankMonitor monitor = createMockedInteractionJankMonitor();
- FrameTracker tracker = createMockedFrameTracker(monitor, null);
- doReturn(tracker).when(monitor).createFrameTracker(any(), any());
+ FrameTracker tracker = createMockedFrameTracker();
+ doReturn(tracker).when(monitor).createFrameTracker(any());
doNothing().when(tracker).begin();
doReturn(true).when(tracker).cancel(anyInt());
- assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
+ assertThat(monitor.begin(mView, Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
verify(tracker).begin();
- verify(monitor).scheduleTimeoutAction(anyInt(), anyLong(), captor.capture());
+ verify(monitor).scheduleTimeoutAction(any(), captor.capture());
Runnable runnable = captor.getValue();
assertThat(runnable).isNotNull();
- mWorker.getThreadHandler().removeCallbacks(runnable);
runnable.run();
- verify(monitor).cancel(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, REASON_CANCEL_TIMEOUT);
+ verify(monitor).cancel(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, REASON_CANCEL_TIMEOUT);
verify(tracker).cancel(REASON_CANCEL_TIMEOUT);
}
@Test
- public void testCujTypeEnumCorrectlyDefined() throws Exception {
- List<Field> cujEnumFields =
- Arrays.stream(InteractionJankMonitor.class.getDeclaredFields())
- .filter(field -> field.getName().startsWith("CUJ_")
- && Modifier.isStatic(field.getModifiers())
- && field.getType() == int.class)
- .collect(Collectors.toList());
-
- HashSet<Integer> allValues = new HashSet<>();
- for (Field field : cujEnumFields) {
- int fieldValue = field.getInt(null);
- assertWithMessage(
- "Field %s must have a mapping to a value in CUJ_TO_STATSD_INTERACTION_TYPE",
- field.getName())
- .that(fieldValue < CUJ_TO_STATSD_INTERACTION_TYPE.length)
- .isTrue();
- assertWithMessage("All CujType values must be unique. Field %s repeats existing value.",
- field.getName())
- .that(allValues.add(fieldValue))
- .isTrue();
- }
- }
-
- @Test
- public void testCujsMapToEnumsCorrectly() {
- List<Field> cujs = Arrays.stream(InteractionJankMonitor.class.getDeclaredFields())
- .filter(f -> f.getName().startsWith("CUJ_")
- && Modifier.isStatic(f.getModifiers())
- && f.getType() == int.class)
- .collect(Collectors.toList());
-
- Map<Integer, String> enumsMap = Arrays.stream(FrameworkStatsLog.class.getDeclaredFields())
- .filter(f -> f.getName().startsWith(ENUM_NAME_PREFIX)
- && !DEPRECATED_VALUES.contains(f.getName())
- && Modifier.isStatic(f.getModifiers())
- && f.getType() == int.class)
- .collect(Collectors.toMap(this::getIntFieldChecked, Field::getName));
-
- assertThat(enumsMap.size() - 1).isEqualTo(cujs.size());
-
- cujs.forEach(f -> {
- final int cuj = getIntFieldChecked(f);
- final String cujName = f.getName();
- final String expectedEnumName = ENUM_NAME_EXCEPTION_MAP.contains(cuj)
- ? ENUM_NAME_EXCEPTION_MAP.get(cuj)
- : formatSimple("%s%s", ENUM_NAME_PREFIX, cujName.substring(4));
- final int enumKey = CUJ_TO_STATSD_INTERACTION_TYPE[cuj];
- final String enumName = enumsMap.get(enumKey);
- final String expectedNameOfCuj = formatSimple("CUJ_%s", getNameOfCuj(cuj));
-
- mExpect
- .withMessage(formatSimple(
- "%s (%d) not matches %s (%d)", cujName, cuj, enumName, enumKey))
- .that(expectedEnumName.equals(enumName))
- .isTrue();
- mExpect
- .withMessage(
- formatSimple("getNameOfCuj(%d) not matches: %s, expected=%s",
- cuj, cujName, expectedNameOfCuj))
- .that(cujName.equals(expectedNameOfCuj))
- .isTrue();
- });
- }
-
- @Test
- public void testCujNameLimit() {
- Arrays.stream(InteractionJankMonitor.class.getDeclaredFields())
- .filter(f -> f.getName().startsWith("CUJ_")
- && Modifier.isStatic(f.getModifiers())
- && f.getType() == int.class)
- .forEach(f -> {
- final int cuj = getIntFieldChecked(f);
- mExpect
- .withMessage(formatSimple(
- "Too long CUJ(%d) name: %s", cuj, getNameOfCuj(cuj)))
- .that(getNameOfCuj(cuj).length())
- .isAtMost(MAX_LENGTH_OF_CUJ_NAME);
- });
- }
-
- @Test
public void testSessionNameLengthLimit() {
- final int cujType = CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
- final String cujName = getNameOfCuj(cujType);
+ final int cujType = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
+ final String cujName = Cuj.getNameOfCuj(cujType);
final String cujTag = "ThisIsTheCujTag";
final String tooLongTag = cujTag.repeat(10);
// Normal case, no postfix.
- Session noPostfix = new Session(cujType, "");
- assertThat(noPostfix.getName()).isEqualTo(formatSimple("J<%s>", cujName));
+ assertThat(generateSessionName(cujName, "")).isEqualTo(formatSimple("J<%s>", cujName));
// Normal case, with postfix.
- Session withPostfix = new Session(cujType, cujTag);
- assertThat(withPostfix.getName()).isEqualTo(formatSimple("J<%s::%s>", cujName, cujTag));
+ assertThat(generateSessionName(cujName, cujTag))
+ .isEqualTo(formatSimple("J<%s::%s>", cujName, cujTag));
// Since the length of the cuj name is tested in another test, no need to test it here.
// Too long postfix case, should trim the postfix and keep the cuj name completed.
final String expectedTrimmedName = formatSimple("J<%s::%s>", cujName,
"ThisIsTheCujTagThisIsTheCujTagThisIsTheCujTagThisIsTheCujTagThi...");
- Session longPostfix = new Session(cujType, tooLongTag);
- assertThat(longPostfix.getName()).isEqualTo(expectedTrimmedName);
+ assertThat(generateSessionName(cujName, tooLongTag)).isEqualTo(expectedTrimmedName);
}
private InteractionJankMonitor createMockedInteractionJankMonitor() {
InteractionJankMonitor monitor = spy(new InteractionJankMonitor(mWorker));
- doReturn(true).when(monitor).shouldMonitor(anyInt());
+ doReturn(true).when(monitor).shouldMonitor();
return monitor;
}
- private FrameTracker createMockedFrameTracker(InteractionJankMonitor monitor,
- FrameTracker.FrameTrackerListener listener) {
- Session session = spy(new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX));
- doReturn(false).when(session).logToStatsd();
-
+ private FrameTracker createMockedFrameTracker() {
ThreadedRendererWrapper threadedRenderer = mock(ThreadedRendererWrapper.class);
doNothing().when(threadedRenderer).addObserver(any());
doNothing().when(threadedRenderer).removeObserver(any());
@@ -342,27 +192,19 @@
Configuration configuration = mock(Configuration.class);
when(configuration.isSurfaceOnly()).thenReturn(false);
when(configuration.getView()).thenReturn(mView);
- when(configuration.getHandler()).thenReturn(mView.getHandler());
when(configuration.getDisplayId()).thenReturn(42);
+ when(configuration.logToStatsd()).thenReturn(false);
+ when(configuration.getHandler()).thenReturn(mHandler);
- FrameTracker tracker = spy(new FrameTracker(monitor, session, mWorker.getThreadHandler(),
+ FrameTracker tracker = spy(new FrameTracker(configuration,
threadedRenderer, viewRoot, surfaceControl, choreographer,
new FrameMetricsWrapper(), new StatsLogWrapper(null),
/* traceThresholdMissedFrames= */ 1,
- /* traceThresholdFrameTimeMillis= */ -1, listener, configuration));
+ /* traceThresholdFrameTimeMillis= */ -1,
+ /* listener */ null));
doNothing().when(tracker).postTraceStartMarker(any());
- doNothing().when(tracker).triggerPerfetto();
- doReturn(configuration.getHandler()).when(tracker).getHandler();
return tracker;
}
-
- private int getIntFieldChecked(Field field) {
- try {
- return field.getInt(null);
- } catch (IllegalAccessException ex) {
- throw new RuntimeException(ex);
- }
- }
}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 2237ba1..aaddf0e 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -169,6 +169,12 @@
"group": "WM_DEBUG_WINDOW_ORGANIZER",
"at": "com\/android\/server\/wm\/TaskOrganizerController.java"
},
+ "-1961637874": {
+ "message": "DeferredDisplayUpdater: applying DisplayInfo immediately",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java"
+ },
"-1949279037": {
"message": "Attempted to add input method window with bad token %s. Aborting.",
"level": "WARN",
@@ -313,6 +319,12 @@
"group": "WM_DEBUG_RESIZE",
"at": "com\/android\/server\/wm\/WindowState.java"
},
+ "-1818910559": {
+ "message": "DeferredDisplayUpdater: applied DisplayInfo after deferring",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java"
+ },
"-1814361639": {
"message": "Set IME snapshot position: (%d, %d)",
"level": "INFO",
@@ -595,6 +607,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-1518132958": {
+ "message": "fractionRendered boundsOverSource=%f",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_TPL",
+ "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+ },
"-1517908912": {
"message": "requestScrollCapture: caught exception dispatching to window.token=%s",
"level": "WARN",
@@ -961,6 +979,12 @@
"group": "WM_DEBUG_CONTENT_RECORDING",
"at": "com\/android\/server\/wm\/ContentRecorder.java"
},
+ "-1209762265": {
+ "message": "Registering listener=%s with id=%d for window=%s with %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TPL",
+ "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+ },
"-1209252064": {
"message": "Clear animatingExit: reason=clearAnimatingFlags win=%s",
"level": "DEBUG",
@@ -1333,6 +1357,12 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/Transition.java"
},
+ "-888703350": {
+ "message": "Skipping %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_TPL",
+ "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+ },
"-883738232": {
"message": "Adding more than one toast window for UID at a time.",
"level": "WARN",
@@ -1909,6 +1939,12 @@
"group": "WM_DEBUG_FOCUS_LIGHT",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "-415346336": {
+ "message": "DeferredDisplayUpdater: partially applying DisplayInfo immediately",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java"
+ },
"-401282500": {
"message": "destroyIfPossible: r=%s destroy returned removed=%s",
"level": "DEBUG",
@@ -1957,6 +1993,12 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/AppTransitionController.java"
},
+ "-376950429": {
+ "message": "DeferredDisplayUpdater: deferring DisplayInfo update",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java"
+ },
"-374767836": {
"message": "setAppVisibility(%s, visible=%b): %s visible=%b mVisibleRequested=%b Callers=%s",
"level": "VERBOSE",
@@ -2803,6 +2845,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "360319850": {
+ "message": "fractionRendered scale=%f",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_TPL",
+ "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+ },
"364992694": {
"message": "freezeDisplayRotation: current rotation=%d, new rotation=%d, caller=%s",
"level": "VERBOSE",
@@ -2983,6 +3031,12 @@
"group": "WM_DEBUG_BACK_PREVIEW",
"at": "com\/android\/server\/wm\/BackNavigationController.java"
},
+ "532771960": {
+ "message": "Adding untrusted state listener=%s with id=%d",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TPL",
+ "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+ },
"535103992": {
"message": "Wallpaper may change! Adjusting",
"level": "VERBOSE",
@@ -3061,6 +3115,12 @@
"group": "WM_DEBUG_DREAM",
"at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
},
+ "605179032": {
+ "message": "checkIfInThreshold fractionRendered=%f alpha=%f currTimeMs=%d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_TPL",
+ "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+ },
"608694300": {
"message": " NEW SURFACE SESSION %s",
"level": "INFO",
@@ -3289,6 +3349,12 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/WindowState.java"
},
+ "824532141": {
+ "message": "lastState=%s newState=%s alpha=%f minAlpha=%f fractionRendered=%f minFractionRendered=%f",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_TPL",
+ "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+ },
"829434921": {
"message": "Draw state now committed in %s",
"level": "VERBOSE",
@@ -3583,6 +3649,12 @@
"group": "WM_SHOW_SURFACE_ALLOC",
"at": "com\/android\/server\/wm\/ScreenRotationAnimation.java"
},
+ "1090378847": {
+ "message": "Checking %d windows",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_TPL",
+ "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+ },
"1100065297": {
"message": "Attempted to get IME policy of a display that does not exist: %d",
"level": "WARN",
@@ -3715,6 +3787,12 @@
"group": "WM_DEBUG_FOCUS",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "1251721200": {
+ "message": "unregister failed, couldn't find deathRecipient for %s with id=%d",
+ "level": "ERROR",
+ "group": "WM_DEBUG_TPL",
+ "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+ },
"1252594551": {
"message": "Window types in WindowContext and LayoutParams.type should match! Type from LayoutParams is %d, but type from WindowContext is %d",
"level": "WARN",
@@ -3853,6 +3931,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/TaskDisplayArea.java"
},
+ "1382634842": {
+ "message": "Unregistering listener=%s with id=%d",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TPL",
+ "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+ },
"1393721079": {
"message": "Starting remote display change: from [rot = %d], to [%dx%d, rot = %d]",
"level": "VERBOSE",
@@ -3901,6 +3985,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "1445704347": {
+ "message": "coveredRegionsAbove updated with %s frame:%s region:%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_TPL",
+ "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+ },
"1448683958": {
"message": "Override pending remote transitionSet=%b adapter=%s",
"level": "INFO",
@@ -4201,6 +4291,12 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimation.java"
},
+ "1786463281": {
+ "message": "Adding trusted state listener=%s with id=%d",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TPL",
+ "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+ },
"1789321832": {
"message": "Then token:%s is invalid. It might be removed",
"level": "WARN",
@@ -4375,6 +4471,12 @@
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
+ "1955470028": {
+ "message": "computeFractionRendered: visibleRegion=%s screenBounds=%s contentSize=%s scale=%f,%f",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_TPL",
+ "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+ },
"1964565370": {
"message": "Starting remote animation",
"level": "INFO",
@@ -4659,6 +4761,9 @@
"WM_DEBUG_TASKS": {
"tag": "WindowManager"
},
+ "WM_DEBUG_TPL": {
+ "tag": "WindowManager"
+ },
"WM_DEBUG_WALLPAPER": {
"tag": "WindowManager"
},
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index dc413b0..a32b435 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -28,7 +28,7 @@
import android.window.IBackAnimationRunner;
import android.window.IOnBackInvokedCallback;
-import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.jank.Cuj.CujType;
import com.android.wm.shell.common.InteractionJankMonitorUtils;
/**
@@ -42,7 +42,7 @@
private final IOnBackInvokedCallback mCallback;
private final IRemoteAnimationRunner mRunner;
- private final @InteractionJankMonitor.CujType int mCujType;
+ private final @CujType int mCujType;
private final Context mContext;
// Whether we are waiting to receive onAnimationStart
@@ -55,7 +55,7 @@
@NonNull IOnBackInvokedCallback callback,
@NonNull IRemoteAnimationRunner runner,
@NonNull Context context,
- @InteractionJankMonitor.CujType int cujType) {
+ @CujType int cujType) {
mCallback = callback;
mRunner = runner;
mCujType = cujType;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java
index ec344d3..86f00b8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java
@@ -23,6 +23,7 @@
import android.view.SurfaceControl;
import android.view.View;
+import com.android.internal.jank.Cuj.CujType;
import com.android.internal.jank.InteractionJankMonitor;
/** Utils class for simplfy InteractionJank trancing call */
@@ -31,11 +32,11 @@
/**
* Begin a trace session.
*
- * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+ * @param cujType the specific {@link CujType}.
* @param view the view to trace
* @param tag the tag to distinguish different flow of same type CUJ.
*/
- public static void beginTracing(@InteractionJankMonitor.CujType int cujType,
+ public static void beginTracing(@CujType int cujType,
@NonNull View view, @Nullable String tag) {
final InteractionJankMonitor.Configuration.Builder builder =
InteractionJankMonitor.Configuration.Builder.withView(cujType, view);
@@ -48,12 +49,12 @@
/**
* Begin a trace session.
*
- * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+ * @param cujType the specific {@link CujType}.
* @param context the context
* @param surface the surface to trace
* @param tag the tag to distinguish different flow of same type CUJ.
*/
- public static void beginTracing(@InteractionJankMonitor.CujType int cujType,
+ public static void beginTracing(@CujType int cujType,
@NonNull Context context, @NonNull SurfaceControl surface, @Nullable String tag) {
final InteractionJankMonitor.Configuration.Builder builder =
InteractionJankMonitor.Configuration.Builder.withSurface(cujType, context, surface);
@@ -66,18 +67,18 @@
/**
* End a trace session.
*
- * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+ * @param cujType the specific {@link CujType}.
*/
- public static void endTracing(@InteractionJankMonitor.CujType int cujType) {
+ public static void endTracing(@CujType int cujType) {
InteractionJankMonitor.getInstance().end(cujType);
}
/**
* Cancel the trace session.
*
- * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+ * @param cujType the specific {@link CujType}.
*/
- public static void cancelTracing(@InteractionJankMonitor.CujType int cujType) {
+ public static void cancelTracing(@CujType int cujType) {
InteractionJankMonitor.getInstance().cancel(cujType);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
index 38ce164..d157ca8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -16,8 +16,8 @@
package com.android.wm.shell.onehanded;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_ONE_HANDED_ENTER_TRANSITION;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_ONE_HANDED_EXIT_TRANSITION;
+import static com.android.internal.jank.Cuj.CUJ_ONE_HANDED_ENTER_TRANSITION;
+import static com.android.internal.jank.Cuj.CUJ_ONE_HANDED_EXIT_TRANSITION;
import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSITION_DIRECTION_EXIT;
import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSITION_DIRECTION_TRIGGER;
@@ -37,6 +37,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.internal.jank.Cuj.CujType;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
@@ -327,7 +328,7 @@
mTransitionCallbacks.add(callback);
}
- void beginCUJTracing(@InteractionJankMonitor.CujType int cujType, @Nullable String tag) {
+ void beginCUJTracing(@CujType int cujType, @Nullable String tag) {
final Map.Entry<WindowContainerToken, SurfaceControl> firstEntry =
getDisplayAreaTokenMap().entrySet().iterator().next();
final InteractionJankMonitor.Configuration.Builder builder =
@@ -339,11 +340,11 @@
mJankMonitor.begin(builder);
}
- void endCUJTracing(@InteractionJankMonitor.CujType int cujType) {
+ void endCUJTracing(@CujType int cujType) {
mJankMonitor.end(cujType);
}
- void cancelCUJTracing(@InteractionJankMonitor.CujType int cujType) {
+ void cancelCUJTracing(@CujType int cujType) {
mJankMonitor.cancel(cujType);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 8eb4a5a..f5c01d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -770,6 +770,7 @@
}
if (mContentOverlay != null) {
mContentOverlay.onAnimationEnd(tx, destBounds);
+ clearContentOverlay();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
index 850b06a0..a2bd47c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
@@ -109,7 +109,7 @@
@Override
public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
- // Do nothing. Color overlay should be fully opaque by now.
+ // Do nothing. Color overlay should be fully opaque by now, ready for fade out.
}
private float[] getContentOverlayColor(Context context) {
@@ -167,7 +167,7 @@
@Override
public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
- atomicTx.remove(mLeash);
+ // Do nothing. Snapshot overlay should be fully opaque by now, ready for fade out.
}
}
@@ -193,19 +193,12 @@
MAX_APP_ICON_SIZE_DP, context.getResources().getDisplayMetrics());
mAppIconSizePx = Math.min(maxAppIconSizePx, appIconSizePx);
- final int appWidth = appBounds.width();
- final int appHeight = appBounds.height();
-
- // In order to have the overlay always cover the pip window during the transition, the
- // overlay will be drawn with the max size of the start and end bounds in different
- // rotation.
- final int overlaySize = Math.max(Math.max(appWidth, appHeight),
- Math.max(destinationBounds.width(), destinationBounds.height())) + 1;
+ final int overlaySize = getOverlaySize(appBounds, destinationBounds);
mOverlayHalfSize = overlaySize >> 1;
// When the activity is in the secondary split, make sure the scaling center is not
// offset.
- mAppBounds = new Rect(0, 0, appWidth, appHeight);
+ mAppBounds = new Rect(0, 0, appBounds.width(), appBounds.height());
mBitmap = Bitmap.createBitmap(overlaySize, overlaySize, Bitmap.Config.ARGB_8888);
prepareAppIconOverlay(appIcon);
@@ -215,6 +208,21 @@
.build();
}
+ /**
+ * Returns the size of the app icon overlay.
+ *
+ * In order to have the overlay always cover the pip window during the transition,
+ * the overlay will be drawn with the max size of the start and end bounds in different
+ * rotation.
+ */
+ public static int getOverlaySize(Rect appBounds, Rect destinationBounds) {
+ final int appWidth = appBounds.width();
+ final int appHeight = appBounds.height();
+
+ return Math.max(Math.max(appWidth, appHeight),
+ Math.max(destinationBounds.width(), destinationBounds.height())) + 1;
+ }
+
@Override
public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) {
tx.show(mLeash);
@@ -248,7 +256,7 @@
@Override
public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
- atomicTx.remove(mLeash);
+ // Do nothing. Icon overlay should be fully opaque by now, ready for fade out.
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index c1164fc..743b1ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -156,74 +156,77 @@
// These callbacks are called on the update thread
private final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
new PipAnimationController.PipAnimationCallback() {
- private boolean mIsCancelled;
- @Override
- public void onPipAnimationStart(TaskInfo taskInfo,
- PipAnimationController.PipTransitionAnimator animator) {
- final int direction = animator.getTransitionDirection();
- mIsCancelled = false;
- sendOnPipTransitionStarted(direction);
- }
+ private boolean mIsCancelled;
- @Override
- public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
- PipAnimationController.PipTransitionAnimator animator) {
- final int direction = animator.getTransitionDirection();
- if (mIsCancelled) {
- sendOnPipTransitionFinished(direction);
- maybePerformFinishResizeCallback();
- return;
- }
- final int animationType = animator.getAnimationType();
- final Rect destinationBounds = animator.getDestinationBounds();
- if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) {
- fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(),
- animator::clearContentOverlay, true /* withStartDelay*/);
- }
- if (mWaitForFixedRotation && animationType == ANIM_TYPE_BOUNDS
- && direction == TRANSITION_DIRECTION_TO_PIP) {
- // Notify the display to continue the deferred orientation change.
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.scheduleFinishEnterPip(mToken, destinationBounds);
- mTaskOrganizer.applyTransaction(wct);
- // The final task bounds will be applied by onFixedRotationFinished so that all
- // coordinates are in new rotation.
- mSurfaceTransactionHelper.round(tx, mLeash, isInPip());
- mDeferredAnimEndTransaction = tx;
- return;
- }
- final boolean isExitPipDirection = isOutPipDirection(direction)
- || isRemovePipDirection(direction);
- if (mPipTransitionState.getTransitionState() != PipTransitionState.EXITING_PIP
- || isExitPipDirection) {
- // execute the finish resize callback if needed after the transaction is committed
- tx.addTransactionCommittedListener(mMainExecutor,
- PipTaskOrganizer.this::maybePerformFinishResizeCallback);
+ @Override
+ public void onPipAnimationStart(TaskInfo taskInfo,
+ PipAnimationController.PipTransitionAnimator animator) {
+ final int direction = animator.getTransitionDirection();
+ mIsCancelled = false;
+ sendOnPipTransitionStarted(direction);
+ }
- // Finish resize as long as we're not exiting PIP, or, if we are, only if this is
- // the end of an exit PIP animation.
- // This is necessary in case there was a resize animation ongoing when exit PIP
- // started, in which case the first resize will be skipped to let the exit
- // operation handle the final resize out of PIP mode. See b/185306679.
- finishResizeDelayedIfNeeded(() -> {
- finishResize(tx, destinationBounds, direction, animationType);
- sendOnPipTransitionFinished(direction);
- });
- }
- }
+ @Override
+ public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
+ PipAnimationController.PipTransitionAnimator animator) {
+ final int direction = animator.getTransitionDirection();
+ if (mIsCancelled) {
+ sendOnPipTransitionFinished(direction);
+ maybePerformFinishResizeCallback();
+ return;
+ }
+ final int animationType = animator.getAnimationType();
+ final Rect destinationBounds = animator.getDestinationBounds();
+ if (isInPipDirection(direction) && mPipOverlay != null) {
+ fadeOutAndRemoveOverlay(mPipOverlay,
+ null /* callback */, true /* withStartDelay*/);
+ }
+ if (mWaitForFixedRotation && animationType == ANIM_TYPE_BOUNDS
+ && direction == TRANSITION_DIRECTION_TO_PIP) {
+ // Notify the display to continue the deferred orientation change.
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.scheduleFinishEnterPip(mToken, destinationBounds);
+ mTaskOrganizer.applyTransaction(wct);
+ // The final task bounds will be applied by onFixedRotationFinished so
+ // that all coordinates are in new rotation.
+ mSurfaceTransactionHelper.round(tx, mLeash, isInPip());
+ mDeferredAnimEndTransaction = tx;
+ return;
+ }
+ final boolean isExitPipDirection = isOutPipDirection(direction)
+ || isRemovePipDirection(direction);
+ if (mPipTransitionState.getTransitionState() != PipTransitionState.EXITING_PIP
+ || isExitPipDirection) {
+ // execute the finish resize callback if needed after the transaction is
+ // committed
+ tx.addTransactionCommittedListener(mMainExecutor,
+ PipTaskOrganizer.this::maybePerformFinishResizeCallback);
- @Override
- public void onPipAnimationCancel(TaskInfo taskInfo,
- PipAnimationController.PipTransitionAnimator animator) {
- final int direction = animator.getTransitionDirection();
- mIsCancelled = true;
- if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) {
- fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(),
- animator::clearContentOverlay, true /* withStartDelay */);
- }
- sendOnPipTransitionCancelled(direction);
- }
- };
+ // Finish resize as long as we're not exiting PIP, or, if we are, only if
+ // this is the end of an exit PIP animation.
+ // This is necessary in case there was a resize animation ongoing when
+ // exit PIP started, in which case the first resize will be skipped to
+ // let the exit operation handle the final resize out of PIP mode.
+ // See b/185306679.
+ finishResizeDelayedIfNeeded(() -> {
+ finishResize(tx, destinationBounds, direction, animationType);
+ sendOnPipTransitionFinished(direction);
+ });
+ }
+ }
+
+ @Override
+ public void onPipAnimationCancel(TaskInfo taskInfo,
+ PipAnimationController.PipTransitionAnimator animator) {
+ final int direction = animator.getTransitionDirection();
+ mIsCancelled = true;
+ if (isInPipDirection(direction) && mPipOverlay != null) {
+ fadeOutAndRemoveOverlay(mPipOverlay,
+ null /* callback */, true /* withStartDelay */);
+ }
+ sendOnPipTransitionCancelled(direction);
+ }
+ };
/**
* Finishes resizing the PiP, delaying the operation if it has to be synced with the PiP menu.
@@ -327,11 +330,18 @@
/**
* An optional overlay used to mask content changing between an app in/out of PiP, only set if
- * {@link PipTransitionState#getInSwipePipToHomeTransition()} is true.
+ * {@link PipTransitionState#getInSwipePipToHomeTransition()} is true, only in gesture nav.
*/
@Nullable
SurfaceControl mSwipePipToHomeOverlay;
+ /**
+ * An optional overlay used to mask content changing between an app in/out of PiP, only set if
+ * {@link PipTransitionState#getInSwipePipToHomeTransition()} is false.
+ */
+ @Nullable
+ SurfaceControl mPipOverlay;
+
public PipTaskOrganizer(Context context,
@NonNull SyncTransactionQueue syncTransactionQueue,
@NonNull PipTransitionState pipTransitionState,
@@ -1766,6 +1776,7 @@
animator.setSnapshotContentOverlay(snapshot, sourceHintRect);
}
}
+ mPipOverlay = animator.getContentOverlayLeash();
// The destination bounds are used for the end rect of animation and the final bounds
// after animation finishes. So after the animation is started, the destination bounds
// can be updated to new rotation (computeRotatedBounds has changed the DisplayLayout
@@ -1879,6 +1890,14 @@
}
private void removeContentOverlay(SurfaceControl surface, Runnable callback) {
+ if (mPipOverlay != null) {
+ if (mPipOverlay != surface) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: trying to remove overlay (%s) which is not local reference (%s)",
+ TAG, surface, mPipOverlay);
+ }
+ mPipOverlay = null;
+ }
if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
// Avoid double removal, which is fatal.
return;
@@ -1907,11 +1926,11 @@
private void cancelCurrentAnimator() {
final PipAnimationController.PipTransitionAnimator<?> animator =
mPipAnimationController.getCurrentAnimator();
+ // remove any overlays if present
+ if (mPipOverlay != null) {
+ removeContentOverlay(mPipOverlay, null /* callback */);
+ }
if (animator != null) {
- if (animator.getContentOverlayLeash() != null) {
- removeContentOverlay(animator.getContentOverlayLeash(),
- animator::clearContentOverlay);
- }
PipAnimationController.quietCancel(animator);
mPipAnimationController.resetAnimatorState();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index fe4980a..0f3c162 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -43,6 +43,7 @@
import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
import android.animation.Animator;
+import android.annotation.IntDef;
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.Context;
@@ -76,6 +77,8 @@
import com.android.wm.shell.util.TransitionUtil;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Optional;
/**
@@ -86,6 +89,18 @@
private static final String TAG = PipTransition.class.getSimpleName();
+ /** No fixed rotation, or fixed rotation state is undefined. */
+ private static final int FIXED_ROTATION_UNDEFINED = 0;
+ /**
+ * Fixed rotation detected via callbacks (see PipController#startSwipePipToHome());
+ * this is used in the swipe PiP to home case, since the transitions itself isn't supposed to
+ * see the fixed rotation.
+ */
+ private static final int FIXED_ROTATION_CALLBACK = 1;
+
+ /** Fixed rotation detected in the incoming transition. */
+ private static final int FIXED_ROTATION_TRANSITION = 2;
+
private final Context mContext;
private final PipTransitionState mPipTransitionState;
private final PipDisplayLayoutState mPipDisplayLayoutState;
@@ -106,17 +121,28 @@
/** The Task window that is currently in PIP windowing mode. */
@Nullable
private WindowContainerToken mCurrentPipTaskToken;
- /** Whether display is in fixed rotation. */
- private boolean mInFixedRotation;
+
+ @IntDef(prefix = { "FIXED_ROTATION_" }, value = {
+ FIXED_ROTATION_UNDEFINED,
+ FIXED_ROTATION_CALLBACK,
+ FIXED_ROTATION_TRANSITION
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FixedRotationState {}
+
+ /** Fixed rotation state of the display. */
+ private @FixedRotationState int mFixedRotationState = FIXED_ROTATION_UNDEFINED;
/**
* The rotation that the display will apply after expanding PiP to fullscreen. This is only
- * meaningful if {@link #mInFixedRotation} is true.
+ * meaningful if {@link #mFixedRotationState} is {@link #FIXED_ROTATION_TRANSITION}.
*/
@Surface.Rotation
private int mEndFixedRotation;
/** Whether the PIP window has fade out for fixed rotation. */
private boolean mHasFadeOut;
+ private Rect mInitBounds = new Rect();
+
/** Used for setting transform to a transaction from animator. */
private final PipAnimationController.PipTransactionHandler mTransactionConsumer =
new PipAnimationController.PipTransactionHandler() {
@@ -181,8 +207,16 @@
@NonNull Transitions.TransitionFinishCallback finishCallback) {
final TransitionInfo.Change currentPipTaskChange = findCurrentPipTaskChange(info);
final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
- mInFixedRotation = fixedRotationChange != null;
- mEndFixedRotation = mInFixedRotation
+ if (mFixedRotationState == FIXED_ROTATION_TRANSITION) {
+ // If we are just about to process potential fixed rotation information,
+ // then fixed rotation state should either be UNDEFINED or CALLBACK.
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "%s: startAnimation() should start with clear fixed rotation state", TAG);
+ mFixedRotationState = FIXED_ROTATION_UNDEFINED;
+ }
+ mFixedRotationState = fixedRotationChange != null
+ ? FIXED_ROTATION_TRANSITION : mFixedRotationState;
+ mEndFixedRotation = mFixedRotationState == FIXED_ROTATION_TRANSITION
? fixedRotationChange.getEndFixedRotation()
: ROTATION_UNDEFINED;
@@ -347,6 +381,10 @@
@Override
public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
@Nullable SurfaceControl.Transaction finishT) {
+ // Transition either finished pre-emptively, got merged, or aborted,
+ // so update fixed rotation state to default.
+ mFixedRotationState = FIXED_ROTATION_UNDEFINED;
+
if (transition != mExitTransition) {
return;
}
@@ -408,7 +446,8 @@
// done at the start. But if it is running fixed rotation, there will be a seamless
// display transition later. So the last rotation transform needs to be kept to
// avoid flickering, and then the display transition will reset the transform.
- if (!mInFixedRotation && mFinishTransaction != null) {
+ if (mFixedRotationState != FIXED_ROTATION_TRANSITION
+ && mFinishTransaction != null) {
mFinishTransaction.merge(tx);
}
} else {
@@ -426,12 +465,27 @@
mSurfaceTransactionHelper.crop(tx, leash, destinationBounds)
.resetScale(tx, leash, destinationBounds)
.round(tx, leash, true /* applyCornerRadius */);
+ if (mPipOrganizer.mSwipePipToHomeOverlay != null && !mInitBounds.isEmpty()) {
+ // Resetting the scale for pinned task while re-adjusting its crop,
+ // also scales the overlay. So we need to update the overlay leash too.
+ Rect overlayBounds = new Rect(destinationBounds);
+ final int overlaySize = PipContentOverlay.PipAppIconOverlay
+ .getOverlaySize(mInitBounds, destinationBounds);
+
+ overlayBounds.offsetTo(
+ (destinationBounds.width() - overlaySize) / 2,
+ (destinationBounds.height() - overlaySize) / 2);
+ mSurfaceTransactionHelper.resetScale(tx,
+ mPipOrganizer.mSwipePipToHomeOverlay, overlayBounds);
+ }
}
+ mInitBounds.setEmpty();
wct.setBoundsChangeTransaction(taskInfo.token, tx);
}
final int displayRotation = taskInfo.getConfiguration().windowConfiguration
.getDisplayRotation();
- if (enteringPip && mInFixedRotation && mEndFixedRotation != displayRotation
+ if (enteringPip && mFixedRotationState == FIXED_ROTATION_TRANSITION
+ && mEndFixedRotation != displayRotation
&& hasValidLeash) {
// Launcher may update the Shelf height during the animation, which will update the
// destination bounds. Because this is in fixed rotation, We need to make sure the
@@ -451,6 +505,8 @@
mFinishTransaction = null;
callFinishCallback(wct);
}
+ // This is the end of transition on the Shell side so update the fixed rotation state.
+ mFixedRotationState = FIXED_ROTATION_UNDEFINED;
finishResizeForMenu(destinationBounds);
}
@@ -467,6 +523,7 @@
// mFinishCallback might be null with an outdated mCurrentPipTaskToken
// for example, when app crashes while in PiP and exit transition has not started
mCurrentPipTaskToken = null;
+ mFixedRotationState = FIXED_ROTATION_UNDEFINED;
if (mFinishCallback == null) return;
mFinishCallback.onTransitionFinished(null /* wct */);
mFinishCallback = null;
@@ -475,6 +532,9 @@
@Override
public void onFixedRotationStarted() {
+ if (mFixedRotationState == FIXED_ROTATION_UNDEFINED) {
+ mFixedRotationState = FIXED_ROTATION_CALLBACK;
+ }
fadeEnteredPipIfNeed(false /* show */);
}
@@ -656,7 +716,7 @@
// Check if it is fixed rotation.
final int rotationDelta;
- if (mInFixedRotation) {
+ if (mFixedRotationState == FIXED_ROTATION_TRANSITION) {
final int startRotation = pipChange.getStartRotation();
final int endRotation = mEndFixedRotation;
rotationDelta = deltaRotation(startRotation, endRotation);
@@ -873,11 +933,13 @@
final int startRotation = pipChange.getStartRotation();
// Check again in case some callers use startEnterAnimation directly so the flag was not
// set in startAnimation, e.g. from DefaultMixedHandler.
- if (!mInFixedRotation) {
+ if (mFixedRotationState != FIXED_ROTATION_TRANSITION) {
mEndFixedRotation = pipChange.getEndFixedRotation();
- mInFixedRotation = mEndFixedRotation != ROTATION_UNDEFINED;
+ mFixedRotationState = mEndFixedRotation != ROTATION_UNDEFINED
+ ? FIXED_ROTATION_TRANSITION : mFixedRotationState;
}
- final int endRotation = mInFixedRotation ? mEndFixedRotation : pipChange.getEndRotation();
+ final int endRotation = mFixedRotationState == FIXED_ROTATION_TRANSITION
+ ? mEndFixedRotation : pipChange.getEndRotation();
setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams,
taskInfo.topActivityInfo);
@@ -888,10 +950,15 @@
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
final Rect currentBounds = pipChange.getStartAbsBounds();
+
+ // Cache the start bounds for overlay manipulations as a part of finishCallback.
+ mInitBounds.set(currentBounds);
+
int rotationDelta = deltaRotation(startRotation, endRotation);
Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
taskInfo.pictureInPictureParams, currentBounds, destinationBounds);
- if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) {
+ if (rotationDelta != Surface.ROTATION_0
+ && mFixedRotationState == FIXED_ROTATION_TRANSITION) {
// Need to get the bounds of new rotation in old rotation for fixed rotation,
computeEnterPipRotatedBounds(rotationDelta, startRotation, endRotation, taskInfo,
destinationBounds, sourceHintRect);
@@ -955,10 +1022,12 @@
} else {
throw new RuntimeException("Unrecognized animation type: " + enterAnimationType);
}
+ mPipOrganizer.mPipOverlay = animator.getContentOverlayLeash();
animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(mEnterExitAnimationDuration);
- if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) {
+ if (rotationDelta != Surface.ROTATION_0
+ && mFixedRotationState == FIXED_ROTATION_TRANSITION) {
// For fixed rotation, the animation destination bounds is in old rotation coordinates.
// Set the destination bounds to new coordinates after the animation is finished.
// ComputeRotatedBounds has changed the DisplayLayout without affecting the animation.
@@ -997,13 +1066,17 @@
@NonNull SurfaceControl leash, @Nullable Rect sourceHintRect,
@NonNull Rect destinationBounds,
@NonNull ActivityManager.RunningTaskInfo pipTaskInfo) {
- if (mInFixedRotation) {
+ if (mFixedRotationState == FIXED_ROTATION_TRANSITION) {
// If rotation changes when returning to home, the transition should contain both the
// entering PiP and the display change (PipController#startSwipePipToHome has updated
// the display layout to new rotation). So it is not expected to see fixed rotation.
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"%s: SwipePipToHome should not use fixed rotation %d", TAG, mEndFixedRotation);
}
+ Rect appBounds = pipTaskInfo.configuration.windowConfiguration.getAppBounds();
+ if (mFixedRotationState == FIXED_ROTATION_CALLBACK && appBounds != null) {
+ mInitBounds.set(appBounds);
+ }
final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay;
if (swipePipToHomeOverlay != null) {
// Launcher fade in the overlay on top of the fullscreen Task. It is possible we
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 20c57fa..04911c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -78,9 +78,9 @@
if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
return;
}
- if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) {
- mPipOrganizer.fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(),
- animator::clearContentOverlay, true /* withStartDelay*/);
+ if (isInPipDirection(direction) && mPipOrganizer.mPipOverlay != null) {
+ mPipOrganizer.fadeOutAndRemoveOverlay(mPipOrganizer.mPipOverlay,
+ null /* callback */, true /* withStartDelay*/);
}
onFinishResize(taskInfo, animator.getDestinationBounds(), direction, tx);
sendOnPipTransitionFinished(direction);
@@ -90,9 +90,9 @@
public void onPipAnimationCancel(TaskInfo taskInfo,
PipAnimationController.PipTransitionAnimator animator) {
final int direction = animator.getTransitionDirection();
- if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) {
- mPipOrganizer.fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(),
- animator::clearContentOverlay, true /* withStartDelay */);
+ if (isInPipDirection(direction) && mPipOrganizer.mPipOverlay != null) {
+ mPipOrganizer.fadeOutAndRemoveOverlay(mPipOrganizer.mPipOverlay,
+ null /* callback */, true /* withStartDelay */);
}
sendOnPipTransitionCancelled(animator.getTransitionDirection());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 56f1c78..7b57097 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -847,9 +847,12 @@
.map(recentTasks -> recentTasks.findTaskInBackground(component, userId1))
.orElse(null);
if (taskInfo != null) {
- startTask(taskInfo.taskId, position, options);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "Start task in background");
+ if (ENABLE_SHELL_TRANSITIONS) {
+ mStageCoordinator.startTask(taskInfo.taskId, position, options);
+ } else {
+ startTask(taskInfo.taskId, position, options);
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Start task in background");
return;
}
if (samePackage(packageName1, packageName2, userId1, userId2)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index be685b5..449bef5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -263,6 +263,10 @@
mStartIntent2 = startIntent2;
mActivatePosition = position;
}
+ SplitRequest(int taskId1, int position) {
+ mActivateTaskId = taskId1;
+ mActivatePosition = position;
+ }
SplitRequest(int taskId1, int taskId2, int position) {
mActivateTaskId = taskId1;
mActivateTaskId2 = taskId2;
@@ -556,6 +560,27 @@
}
}
+ /** Use this method to launch an existing Task via a taskId */
+ void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
+ mSplitRequest = new SplitRequest(taskId, position);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
+ wct.startTask(taskId, options);
+ // If this should be mixed, send the task to avoid split handle transition directly.
+ if (mMixedHandler != null && mMixedHandler.shouldSplitEnterMixed(taskId, mTaskOrganizer)) {
+ mTaskOrganizer.applyTransaction(wct);
+ return;
+ }
+
+ // If split screen is not activated, we're expecting to open a pair of apps to split.
+ final int extraTransitType = mMainStage.isActive()
+ ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
+ prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering);
+
+ mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this,
+ extraTransitType, !mIsDropEntering);
+ }
+
/** Launches an activity into split. */
void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
@Nullable Bundle options) {
@@ -1593,7 +1618,9 @@
// Ensure to evict old splitting tasks because the new split pair might be composed by
// one of the splitting tasks, evicting the task when finishing entering transition
// won't guarantee to put the task to the indicated new position.
- mMainStage.evictAllChildren(wct);
+ if (!mIsDropEntering) {
+ mMainStage.evictAllChildren(wct);
+ }
mMainStage.reparentTopTask(wct);
prepareSplitLayout(wct, resizeAnim);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index ce7fef2..9f20f49 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -48,9 +48,9 @@
import android.window.WindowContainerTransaction;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.common.split.SplitScreenUtils;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.pip.PipTransitionController;
@@ -298,7 +298,7 @@
mixed.mLeftoversHandler = handler.first;
mActiveTransitions.add(mixed);
return handler.second;
- } else if (mUnfoldHandler != null && mUnfoldHandler.hasUnfold(request)) {
+ } else if (mUnfoldHandler != null && mUnfoldHandler.shouldPlayUnfoldAnimation(request)) {
final WindowContainerTransaction wct =
mUnfoldHandler.handleRequest(transition, request);
if (wct != null) {
@@ -902,6 +902,18 @@
return false;
}
+ /** Use to when split use taskId to enter, check if this enter transition should be mixed or
+ * not.*/
+ public boolean shouldSplitEnterMixed(int taskId, ShellTaskOrganizer shellTaskOrganizer) {
+ // Check if this intent package is same as pip one or not, if true we want let the pip
+ // task enter split.
+ if (mPipHandler != null) {
+ return mPipHandler.isInPipPackage(
+ SplitScreenUtils.getPackageName(taskId, shellTaskOrganizer));
+ }
+ return false;
+ }
+
/** @return whether the transition-request represents a pip-entry. */
public boolean requestHasPipEnter(TransitionRequestInfo request) {
return mPipHandler.requestHasPipEnter(request);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index 07c5429..20ff79f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -106,7 +106,7 @@
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull TransitionFinishCallback finishCallback) {
- if (hasUnfold(info) && transition != mTransition) {
+ if (shouldPlayUnfoldAnimation(info) && transition != mTransition) {
// Take over transition that has unfold, we might receive it if no other handler
// accepted request in handleRequest, e.g. for rotation + unfold or
// TRANSIT_NONE + unfold transitions
@@ -213,14 +213,36 @@
}
/** Whether `request` contains an unfold action. */
- public boolean hasUnfold(@NonNull TransitionRequestInfo request) {
+ public boolean shouldPlayUnfoldAnimation(@NonNull TransitionRequestInfo request) {
+ // Unfold animation won't play when animations are disabled
+ if (!ValueAnimator.areAnimatorsEnabled()) return false;
+
return (request.getType() == TRANSIT_CHANGE
&& request.getDisplayChange() != null
- && request.getDisplayChange().isPhysicalDisplayChanged());
+ && isUnfoldDisplayChange(request.getDisplayChange()));
+ }
+
+ private boolean isUnfoldDisplayChange(
+ @NonNull TransitionRequestInfo.DisplayChange displayChange) {
+ if (!displayChange.isPhysicalDisplayChanged()) {
+ return false;
+ }
+
+ if (displayChange.getStartAbsBounds() == null || displayChange.getEndAbsBounds() == null) {
+ return false;
+ }
+
+ // Handle only unfolding, currently we don't have an animation when folding
+ final int endArea =
+ displayChange.getEndAbsBounds().width() * displayChange.getEndAbsBounds().height();
+ final int startArea = displayChange.getStartAbsBounds().width()
+ * displayChange.getStartAbsBounds().height();
+
+ return endArea > startArea;
}
/** Whether `transitionInfo` contains an unfold action. */
- public boolean hasUnfold(@NonNull TransitionInfo transitionInfo) {
+ public boolean shouldPlayUnfoldAnimation(@NonNull TransitionInfo transitionInfo) {
// Unfold animation won't play when animations are disabled
if (!ValueAnimator.areAnimatorsEnabled()) return false;
@@ -250,7 +272,7 @@
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
- if (hasUnfold(request)) {
+ if (shouldPlayUnfoldAnimation(request)) {
mTransition = transition;
return new WindowContainerTransaction();
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 99cd4f3..855b7ee 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -235,7 +235,7 @@
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
SPLIT_POSITION_TOP_OR_LEFT, null);
- verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
+ verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
isNull());
verify(mSplitScreenController, never()).supportMultiInstancesSplit(any());
verify(mStageCoordinator, never()).switchSplitPosition(any());
@@ -243,7 +243,6 @@
@Test
public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() {
- doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
@@ -260,8 +259,8 @@
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
SPLIT_POSITION_TOP_OR_LEFT, null);
-
- verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
+ verify(mSplitScreenController, never()).supportMultiInstancesSplit(any());
+ verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
isNull());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
index 6d73c12..fc1fe1c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
@@ -98,10 +98,13 @@
}
@Test
- public void handleRequest_physicalDisplayChange_handlesTransition() {
+ public void handleRequest_physicalDisplayChangeUnfold_handlesTransition() {
ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
- Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(true);
+ Display.DEFAULT_DISPLAY)
+ .setPhysicalDisplayChanged(true)
+ .setStartAbsBounds(new Rect(0, 0, 100, 100))
+ .setEndAbsBounds(new Rect(0, 0, 200, 200));
TransitionRequestInfo requestInfo = new TransitionRequestInfo(TRANSIT_CHANGE,
triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */);
@@ -112,6 +115,23 @@
}
@Test
+ public void handleRequest_physicalDisplayChangeFold_doesNotHandleTransition() {
+ ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
+ TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
+ Display.DEFAULT_DISPLAY)
+ .setPhysicalDisplayChanged(true)
+ .setStartAbsBounds(new Rect(0, 0, 200, 200))
+ .setEndAbsBounds(new Rect(0, 0, 100, 100));
+ TransitionRequestInfo requestInfo = new TransitionRequestInfo(TRANSIT_CHANGE,
+ triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */);
+
+ WindowContainerTransaction result = mUnfoldTransitionHandler.handleRequest(mTransition,
+ requestInfo);
+
+ assertThat(result).isNull();
+ }
+
+ @Test
public void handleRequest_noPhysicalDisplayChange_doesNotHandleTransition() {
ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
@@ -306,7 +326,10 @@
private TransitionRequestInfo createUnfoldTransitionRequestInfo() {
ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
- Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(true);
+ Display.DEFAULT_DISPLAY)
+ .setPhysicalDisplayChanged(true)
+ .setStartAbsBounds(new Rect(0, 0, 100, 100))
+ .setEndAbsBounds(new Rect(0, 0, 200, 200));
return new TransitionRequestInfo(TRANSIT_CHANGE,
triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */);
}
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 65e1605..bba9c97 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -218,6 +218,15 @@
mLocked.presentation = presentation;
+ if (input_flags::enable_pointer_choreographer()) {
+ // When pointer choreographer is enabled, the presentation mode is only set once when the
+ // PointerController is constructed, before the display viewport is provided.
+ // TODO(b/293587049): Clean up the PointerController interface after pointer choreographer
+ // is permanently enabled. The presentation can be set in the constructor.
+ mCursorController.setStylusHoverMode(presentation == Presentation::STYLUS_HOVER);
+ return;
+ }
+
if (!mCursorController.isViewportValid()) {
return;
}
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 3c0b002..07f63e5 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -62,3 +62,10 @@
description: "Allows clients of privileged MediaRouter2 that hold INTERACT_ACROSS_USERS_FULL to control routing across users."
bug: "288580225"
}
+
+flag {
+ name: "enable_use_of_bluetooth_device_get_alias_for_mr2info_get_name"
+ namespace: "media_solutions"
+ description: "Use BluetoothDevice.getAlias to populate the name of Bluetooth MediaRoute2Infos."
+ bug: "314324170"
+}
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 60bf48c..8b136da 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@
allprojects {
extra["androidTop"] = androidTop
- extra["jetpackComposeVersion"] = "1.6.0-beta01"
+ extra["jetpackComposeVersion"] = "1.6.0-beta02"
}
subprojects {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 90c7d46..f4edb36 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -48,6 +48,7 @@
import com.android.settingslib.spa.gallery.preference.SwitchPreferencePageProvider
import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider
import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider
+import com.android.settingslib.spa.gallery.scaffold.SuwScaffoldPageProvider
import com.android.settingslib.spa.gallery.ui.CategoryPageProvider
import com.android.settingslib.spa.gallery.ui.CopyablePageProvider
import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
@@ -100,6 +101,7 @@
SettingsExposedDropdownMenuCheckBoxProvider,
SettingsTextFieldPasswordPageProvider,
SearchScaffoldPageProvider,
+ SuwScaffoldPageProvider,
CardPageProvider,
CopyablePageProvider,
),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
index 1d897f7..6a2e598 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
@@ -43,6 +43,7 @@
import com.android.settingslib.spa.gallery.page.SliderPageProvider
import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider
import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider
+import com.android.settingslib.spa.gallery.scaffold.SuwScaffoldPageProvider
import com.android.settingslib.spa.gallery.ui.CategoryPageProvider
import com.android.settingslib.spa.gallery.ui.CopyablePageProvider
import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
@@ -59,6 +60,7 @@
OperateListPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
ArgumentPageProvider.buildInjectEntry("foo")!!.setLink(fromPage = owner).build(),
SearchScaffoldPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ SuwScaffoldPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
SliderPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
SpinnerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
SettingsPagerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt
new file mode 100644
index 0000000..6fc8de3
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.scaffold
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.SignalCellularAlt
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.gallery.R
+import com.android.settingslib.spa.widget.illustration.Illustration
+import com.android.settingslib.spa.widget.illustration.IllustrationModel
+import com.android.settingslib.spa.widget.illustration.ResourceType
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.BottomAppBarButton
+import com.android.settingslib.spa.widget.scaffold.SuwScaffold
+import com.android.settingslib.spa.widget.ui.SettingsBody
+
+private const val TITLE = "Sample SuwScaffold"
+
+object SuwScaffoldPageProvider : SettingsPageProvider {
+ override val name = "SuwScaffold"
+
+ private val owner = createSettingsPage()
+
+ fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = owner)
+ .setUiLayoutFn {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ Page()
+ }
+}
+
+@Composable
+private fun Page() {
+ SuwScaffold(
+ imageVector = Icons.Outlined.SignalCellularAlt,
+ title = "Connect to mobile network",
+ actionButton = BottomAppBarButton("Next") {},
+ dismissButton = BottomAppBarButton("Cancel") {},
+ ) {
+ Column(Modifier.padding(SettingsDimension.itemPadding)) {
+ SettingsBody("To add another SIM, download a new eSIM.")
+ }
+ Illustration(object : IllustrationModel {
+ override val resId = R.drawable.accessibility_captioning_banner
+ override val resourceType = ResourceType.IMAGE
+ })
+ Column(Modifier.padding(SettingsDimension.itemPadding)) {
+ SettingsBody("To add another SIM, download a new eSIM.")
+ }
+ Illustration(object : IllustrationModel {
+ override val resId = R.drawable.accessibility_captioning_banner
+ override val resourceType = ResourceType.IMAGE
+ })
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index acd90f3..7eccfe5 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -57,7 +57,7 @@
api("androidx.slice:slice-builders:1.1.0-alpha02")
api("androidx.slice:slice-core:1.1.0-alpha02")
api("androidx.slice:slice-view:1.1.0-alpha02")
- api("androidx.compose.material3:material3:1.2.0-alpha11")
+ api("androidx.compose.material3:material3:1.2.0-alpha12")
api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 5a1120e..c143390 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -37,11 +37,13 @@
val itemPaddingAround = 8.dp
val itemDividerHeight = 32.dp
+ val iconLarge = 48.dp
+
/** The size when app icon is displayed in list. */
val appIconItemSize = 32.dp
/** The size when app icon is displayed in App info page. */
- val appIconInfoSize = 48.dp
+ val appIconInfoSize = iconLarge
/** The vertical padding for buttons. */
val buttonPaddingVertical = 12.dp
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
index 3819a10..e0dd4e1 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
@@ -34,6 +34,7 @@
fun SettingsOutlinedTextField(
value: String,
label: String,
+ errorMessage: String? = null,
singleLine: Boolean = true,
enabled: Boolean = true,
onTextChange: (String) -> Unit,
@@ -49,6 +50,12 @@
},
singleLine = singleLine,
enabled = enabled,
+ isError = errorMessage != null,
+ supportingText = {
+ if (errorMessage != null) {
+ Text(text = errorMessage)
+ }
+ }
)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt
new file mode 100644
index 0000000..354b95d
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.scaffold
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.Button
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.toMediumWeight
+
+data class BottomAppBarButton(
+ val text: String,
+ val onClick: () -> Unit,
+)
+
+@Composable
+fun SuwScaffold(
+ imageVector: ImageVector,
+ title: String,
+ actionButton: BottomAppBarButton? = null,
+ dismissButton: BottomAppBarButton? = null,
+ content: @Composable ColumnScope.() -> Unit,
+) {
+ ActivityTitle(title)
+ Scaffold { innerPadding ->
+ BoxWithConstraints(
+ Modifier
+ .padding(innerPadding)
+ .padding(top = SettingsDimension.itemPaddingAround)
+ ) {
+ // Use single column layout in portrait, two columns in landscape.
+ val useSingleColumn = maxWidth < maxHeight
+ if (useSingleColumn) {
+ Column {
+ Column(
+ Modifier
+ .weight(1f)
+ .verticalScroll(rememberScrollState())
+ ) {
+ Header(imageVector, title)
+ content()
+ }
+ BottomBar(actionButton, dismissButton)
+ }
+ } else {
+ Column(Modifier.padding(horizontal = SettingsDimension.itemPaddingAround)) {
+ Row((Modifier.weight(1f))) {
+ Box(Modifier.weight(1f)) {
+ Header(imageVector, title)
+ }
+ Column(
+ Modifier
+ .weight(1f)
+ .verticalScroll(rememberScrollState())) {
+ content()
+ }
+ }
+ BottomBar(actionButton, dismissButton)
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun Header(
+ imageVector: ImageVector,
+ title: String
+) {
+ Column(Modifier.padding(SettingsDimension.itemPadding)) {
+ Icon(
+ imageVector = imageVector,
+ contentDescription = null,
+ modifier = Modifier
+ .padding(vertical = SettingsDimension.itemPaddingAround)
+ .size(SettingsDimension.iconLarge),
+ tint = MaterialTheme.colorScheme.primary,
+ )
+ Text(
+ text = title,
+ modifier = Modifier.padding(vertical = SettingsDimension.itemPaddingVertical),
+ color = MaterialTheme.colorScheme.onSurface,
+ style = MaterialTheme.typography.displaySmall,
+ )
+ }
+}
+
+@Composable
+private fun BottomBar(
+ actionButton: BottomAppBarButton?,
+ dismissButton: BottomAppBarButton?,
+) {
+ Row(modifier = Modifier.padding(SettingsDimension.itemPaddingAround)) {
+ dismissButton?.apply {
+ TextButton(onClick) {
+ ActionText(text)
+ }
+ }
+ Spacer(modifier = Modifier.weight(1f))
+ actionButton?.apply {
+ Button(onClick) {
+ ActionText(text)
+ }
+ }
+ }
+}
+
+@Composable
+private fun ActionText(text: String) {
+ Text(
+ text = text,
+ style = MaterialTheme.typography.bodyMedium.toMediumWeight(),
+ )
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SuwScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SuwScaffoldTest.kt
new file mode 100644
index 0000000..35c9f78
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SuwScaffoldTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.scaffold
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.SignalCellularAlt
+import androidx.compose.material3.Text
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SuwScaffoldTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun suwScaffold_titleIsDisplayed() {
+ composeTestRule.setContent {
+ SuwScaffold(imageVector = Icons.Outlined.SignalCellularAlt, title = TITLE) {
+ Text(text = "AAA")
+ Text(text = "BBB")
+ }
+ }
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun suwScaffold_itemsAreDisplayed() {
+ composeTestRule.setContent {
+ SuwScaffold(imageVector = Icons.Outlined.SignalCellularAlt, title = TITLE) {
+ Text(text = "AAA")
+ Text(text = "BBB")
+ }
+ }
+
+ composeTestRule.onNodeWithText("AAA").assertIsDisplayed()
+ composeTestRule.onNodeWithText("BBB").assertIsDisplayed()
+ }
+
+ @Test
+ fun suwScaffold_actionButtonDisplayed() {
+ composeTestRule.setContent {
+ SuwScaffold(
+ imageVector = Icons.Outlined.SignalCellularAlt,
+ title = TITLE,
+ actionButton = BottomAppBarButton(TEXT) {},
+ ) {}
+ }
+
+ composeTestRule.onNodeWithText(TEXT).assertIsDisplayed()
+ }
+
+ @Test
+ fun suwScaffold_dismissButtonDisplayed() {
+ composeTestRule.setContent {
+ SuwScaffold(
+ imageVector = Icons.Outlined.SignalCellularAlt,
+ title = TITLE,
+ dismissButton = BottomAppBarButton(TEXT) {},
+ ) {}
+ }
+
+ composeTestRule.onNodeWithText(TEXT).assertIsDisplayed()
+ }
+
+ private companion object {
+ const val TITLE = "Title"
+ const val TEXT = "Text"
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
index 25833b3..5aee8cd 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
@@ -16,7 +16,7 @@
package com.android.settingslib.core.instrumentation;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_SETTINGS_TOGGLE;
+import static com.android.internal.jank.Cuj.CUJ_SETTINGS_TOGGLE;
import static com.android.settingslib.core.instrumentation.SettingsJankMonitor.MONITORED_ANIMATION_DURATION_MS;
import static com.google.common.truth.Truth.assertThat;
@@ -34,8 +34,8 @@
import androidx.preference.SwitchPreference;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.internal.jank.Cuj.CujType;
import com.android.internal.jank.InteractionJankMonitor;
-import com.android.internal.jank.InteractionJankMonitor.CujType;
import com.android.settingslib.testutils.OverpoweredReflectionHelper;
import com.android.settingslib.testutils.shadow.ShadowInteractionJankMonitor;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 7cec99d..8f459c6 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -95,7 +95,9 @@
static final int SETTINGS_VERSION_NEW_ENCODING = 121;
+ // LINT.IfChange
public static final int MAX_LENGTH_PER_STRING = 32768;
+ // LINT.ThenChange(/services/core/java/com/android/server/audio/AudioDeviceInventory.java:settings_max_length_per_string)
private static final long WRITE_SETTINGS_DELAY_MILLIS = 200;
private static final long MAX_WRITE_SETTINGS_DELAY_MILLIS = 2000;
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 187d073..168039e 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -33,8 +33,8 @@
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
import com.android.app.animation.Interpolators
+import com.android.internal.jank.Cuj.CujType
import com.android.internal.jank.InteractionJankMonitor
-import com.android.internal.jank.InteractionJankMonitor.CujType
import com.android.systemui.util.maybeForceFullscreen
import com.android.systemui.util.registerAnimationOnBackInvoked
import kotlin.math.roundToInt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index af35ea4..b738e2b 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -33,6 +33,7 @@
import android.view.ViewGroup
import android.view.ViewGroupOverlay
import android.widget.FrameLayout
+import com.android.internal.jank.Cuj.CujType
import com.android.internal.jank.InteractionJankMonitor
import java.util.LinkedList
import kotlin.math.min
@@ -58,7 +59,7 @@
/** The view that will be ghosted and from which the background will be extracted. */
private val ghostedView: View,
- /** The [InteractionJankMonitor.CujType] associated to this animation. */
+ /** The [CujType] associated to this animation. */
private val cujType: Int? = null,
private var interactionJankMonitor: InteractionJankMonitor =
InteractionJankMonitor.getInstance(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index 1a653c3..47f5663 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalFoundationApi::class)
+
package com.android.systemui.bouncer.ui.composable
import android.app.AlertDialog
@@ -29,18 +31,18 @@
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowDown
@@ -51,6 +53,7 @@
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
+import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@@ -78,6 +81,7 @@
import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.animation.scene.transitions
import com.android.compose.modifiers.thenIf
+import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
@@ -100,37 +104,41 @@
dialogFactory: BouncerDialogFactory,
modifier: Modifier = Modifier,
) {
- val isFullScreenUserSwitcherEnabled = viewModel.isUserSwitcherVisible
val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsState()
val layout = calculateLayout(isSideBySideSupported = isSideBySideSupported)
- when (layout) {
- BouncerSceneLayout.STANDARD ->
- StandardLayout(
- viewModel = viewModel,
- dialogFactory = dialogFactory,
- modifier = modifier,
- )
- BouncerSceneLayout.SIDE_BY_SIDE ->
- SideBySideLayout(
- viewModel = viewModel,
- dialogFactory = dialogFactory,
- isUserSwitcherVisible = isFullScreenUserSwitcherEnabled,
- modifier = modifier,
- )
- BouncerSceneLayout.STACKED ->
- StackedLayout(
- viewModel = viewModel,
- dialogFactory = dialogFactory,
- isUserSwitcherVisible = isFullScreenUserSwitcherEnabled,
- modifier = modifier,
- )
- BouncerSceneLayout.SPLIT ->
- SplitLayout(
- viewModel = viewModel,
- dialogFactory = dialogFactory,
- modifier = modifier,
- )
+ Box(
+ // Allows the content within each of the layouts to react to the appearance and
+ // disappearance of the IME, which is also known as the software keyboard.
+ //
+ // Despite the keyboard only being part of the password bouncer, adding it at this level is
+ // both necessary to properly handle the keyboard in all layouts and harmless in cases when
+ // the keyboard isn't used (like the PIN or pattern auth methods).
+ modifier = modifier.imePadding(),
+ ) {
+ when (layout) {
+ BouncerSceneLayout.STANDARD_BOUNCER ->
+ StandardLayout(
+ viewModel = viewModel,
+ )
+ BouncerSceneLayout.BESIDE_USER_SWITCHER ->
+ BesideUserSwitcherLayout(
+ viewModel = viewModel,
+ )
+ BouncerSceneLayout.BELOW_USER_SWITCHER ->
+ BelowUserSwitcherLayout(
+ viewModel = viewModel,
+ )
+ BouncerSceneLayout.SPLIT_BOUNCER ->
+ SplitLayout(
+ viewModel = viewModel,
+ )
+ }
+
+ Dialog(
+ viewModel = viewModel,
+ dialogFactory = dialogFactory,
+ )
}
}
@@ -141,15 +149,333 @@
@Composable
private fun StandardLayout(
viewModel: BouncerViewModel,
- dialogFactory: BouncerDialogFactory,
modifier: Modifier = Modifier,
- layout: BouncerSceneLayout = BouncerSceneLayout.STANDARD,
- outputOnly: Boolean = false,
+) {
+ val isHeightExpanded =
+ LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded
+
+ FoldAware(
+ modifier =
+ modifier.padding(
+ top = 92.dp,
+ bottom = 48.dp,
+ ),
+ viewModel = viewModel,
+ aboveFold = {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ StatusMessage(
+ viewModel = viewModel,
+ modifier = Modifier,
+ )
+
+ OutputArea(
+ viewModel = viewModel,
+ modifier = Modifier.padding(top = if (isHeightExpanded) 96.dp else 64.dp),
+ )
+ }
+ },
+ belowFold = {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ Box(
+ modifier = Modifier.weight(1f),
+ ) {
+ InputArea(
+ viewModel = viewModel,
+ pinButtonRowVerticalSpacing = 12.dp,
+ centerPatternDotsVertically = false,
+ modifier = Modifier.align(Alignment.BottomCenter),
+ )
+ }
+
+ ActionArea(
+ viewModel = viewModel,
+ modifier = Modifier.padding(top = 48.dp),
+ )
+ }
+ },
+ )
+}
+
+/**
+ * Renders the bouncer UI in split mode, with half on one side and half on the other side, swappable
+ * by double-tapping on the side.
+ */
+@Composable
+private fun SplitLayout(
+ viewModel: BouncerViewModel,
+ modifier: Modifier = Modifier,
+) {
+ val authMethod by viewModel.authMethodViewModel.collectAsState()
+
+ Row(
+ modifier =
+ modifier
+ .fillMaxHeight()
+ .padding(
+ horizontal = 24.dp,
+ vertical = if (authMethod is PasswordBouncerViewModel) 24.dp else 48.dp,
+ ),
+ ) {
+ // Left side (in left-to-right locales).
+ Box(
+ modifier = Modifier.fillMaxHeight().weight(1f),
+ ) {
+ when (authMethod) {
+ is PinBouncerViewModel -> {
+ StatusMessage(
+ viewModel = viewModel,
+ modifier = Modifier.align(Alignment.TopCenter),
+ )
+
+ OutputArea(viewModel = viewModel, modifier = Modifier.align(Alignment.Center))
+
+ ActionArea(
+ viewModel = viewModel,
+ modifier = Modifier.align(Alignment.BottomCenter).padding(top = 48.dp),
+ )
+ }
+ is PatternBouncerViewModel -> {
+ StatusMessage(
+ viewModel = viewModel,
+ modifier = Modifier.align(Alignment.TopCenter),
+ )
+
+ ActionArea(
+ viewModel = viewModel,
+ modifier = Modifier.align(Alignment.BottomCenter).padding(vertical = 48.dp),
+ )
+ }
+ is PasswordBouncerViewModel -> {
+ ActionArea(
+ viewModel = viewModel,
+ modifier = Modifier.align(Alignment.BottomCenter),
+ )
+ }
+ else -> Unit
+ }
+ }
+
+ // Right side (in right-to-left locales).
+ Box(
+ modifier = Modifier.fillMaxHeight().weight(1f),
+ ) {
+ when (authMethod) {
+ is PinBouncerViewModel,
+ is PatternBouncerViewModel -> {
+ InputArea(
+ viewModel = viewModel,
+ pinButtonRowVerticalSpacing = 8.dp,
+ centerPatternDotsVertically = true,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
+ is PasswordBouncerViewModel -> {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.fillMaxWidth().align(Alignment.Center),
+ ) {
+ StatusMessage(
+ viewModel = viewModel,
+ )
+
+ OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp))
+ }
+ }
+ else -> Unit
+ }
+ }
+ }
+}
+
+/**
+ * Arranges the bouncer contents and user switcher contents side-by-side, supporting a double tap
+ * anywhere on the background to flip their positions.
+ */
+@Composable
+private fun BesideUserSwitcherLayout(
+ viewModel: BouncerViewModel,
+ modifier: Modifier = Modifier,
+) {
+ val layoutDirection = LocalLayoutDirection.current
+ val isLeftToRight = layoutDirection == LayoutDirection.Ltr
+ val (isSwapped, setSwapped) = rememberSaveable(isLeftToRight) { mutableStateOf(!isLeftToRight) }
+ val isHeightExpanded =
+ LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded
+ val authMethod by viewModel.authMethodViewModel.collectAsState()
+
+ Row(
+ modifier =
+ modifier.pointerInput(Unit) {
+ detectTapGestures(
+ onDoubleTap = { offset ->
+ // Depending on where the user double tapped, switch the elements such that
+ // the endContent is closer to the side that was double tapped.
+ setSwapped(offset.x < size.width / 2)
+ }
+ )
+ },
+ ) {
+ val animatedOffset by
+ animateFloatAsState(
+ targetValue =
+ if (!isSwapped) {
+ // A non-swapped element has its natural placement so it's not offset.
+ 0f
+ } else if (isLeftToRight) {
+ // A swapped element has its elements offset horizontally. In the case of
+ // LTR locales, this means pushing the element to the right, hence the
+ // positive number.
+ 1f
+ } else {
+ // A swapped element has its elements offset horizontally. In the case of
+ // RTL locales, this means pushing the element to the left, hence the
+ // negative number.
+ -1f
+ },
+ label = "offset",
+ )
+
+ fun Modifier.swappable(inversed: Boolean = false): Modifier {
+ return graphicsLayer {
+ translationX =
+ size.width *
+ animatedOffset *
+ if (inversed) {
+ // A negative sign is used to make sure this is offset in the direction
+ // that's opposite to the direction that the user switcher is pushed in.
+ -1
+ } else {
+ 1
+ }
+ alpha = animatedAlpha(animatedOffset)
+ }
+ }
+
+ UserSwitcher(
+ viewModel = viewModel,
+ modifier = Modifier.weight(1f).align(Alignment.CenterVertically).swappable(),
+ )
+
+ FoldAware(
+ modifier =
+ Modifier.weight(1f)
+ .padding(
+ if (isHeightExpanded) {
+ PaddingValues(vertical = 128.dp)
+ } else {
+ PaddingValues(top = 94.dp, bottom = 48.dp)
+ }
+ )
+ .swappable(inversed = true),
+ viewModel = viewModel,
+ aboveFold = {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ StatusMessage(
+ viewModel = viewModel,
+ )
+
+ OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp))
+ }
+ },
+ belowFold = {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ val isOutputAreaVisible = authMethod !is PatternBouncerViewModel
+ // If there is an output area and the window is not tall enough, spacing needs
+ // to be added between the input and the output areas (otherwise the two get
+ // very squished together).
+ val addSpacingBetweenOutputAndInput = isOutputAreaVisible && !isHeightExpanded
+
+ Box(
+ modifier =
+ Modifier.weight(1f)
+ .padding(top = (if (addSpacingBetweenOutputAndInput) 24 else 0).dp),
+ ) {
+ InputArea(
+ viewModel = viewModel,
+ pinButtonRowVerticalSpacing = 12.dp,
+ centerPatternDotsVertically = true,
+ modifier = Modifier.align(Alignment.BottomCenter),
+ )
+ }
+
+ ActionArea(
+ viewModel = viewModel,
+ modifier = Modifier.padding(top = 48.dp),
+ )
+ }
+ },
+ )
+ }
+}
+
+/** Arranges the bouncer contents and user switcher contents one on top of the other, vertically. */
+@Composable
+private fun BelowUserSwitcherLayout(
+ viewModel: BouncerViewModel,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ modifier =
+ modifier.padding(
+ vertical = 128.dp,
+ )
+ ) {
+ UserSwitcher(
+ viewModel = viewModel,
+ modifier = Modifier.fillMaxWidth(),
+ )
+
+ Spacer(Modifier.weight(1f))
+
+ Box(modifier = Modifier.fillMaxWidth()) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ StatusMessage(
+ viewModel = viewModel,
+ )
+
+ OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp))
+
+ InputArea(
+ viewModel = viewModel,
+ pinButtonRowVerticalSpacing = 12.dp,
+ centerPatternDotsVertically = true,
+ modifier = Modifier.padding(top = 128.dp),
+ )
+
+ ActionArea(
+ viewModel = viewModel,
+ modifier = Modifier.padding(top = 48.dp),
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun FoldAware(
+ viewModel: BouncerViewModel,
+ aboveFold: @Composable BoxScope.() -> Unit,
+ belowFold: @Composable BoxScope.() -> Unit,
+ modifier: Modifier = Modifier,
) {
val foldPosture: FoldPosture by foldPosture()
val isSplitAroundTheFoldRequired by viewModel.isFoldSplitRequired.collectAsState()
- val isSplitAroundTheFold =
- foldPosture == FoldPosture.Tabletop && !outputOnly && isSplitAroundTheFoldRequired
+ val isSplitAroundTheFold = foldPosture == FoldPosture.Tabletop && isSplitAroundTheFoldRequired
val currentSceneKey =
if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey
@@ -160,115 +486,57 @@
modifier = modifier,
) {
scene(SceneKeys.ContiguousSceneKey) {
- FoldSplittable(
- viewModel = viewModel,
- dialogFactory = dialogFactory,
- layout = layout,
- outputOnly = outputOnly,
+ FoldableScene(
+ aboveFold = aboveFold,
+ belowFold = belowFold,
isSplit = false,
)
}
scene(SceneKeys.SplitSceneKey) {
- FoldSplittable(
- viewModel = viewModel,
- dialogFactory = dialogFactory,
- layout = layout,
- outputOnly = outputOnly,
+ FoldableScene(
+ aboveFold = aboveFold,
+ belowFold = belowFold,
isSplit = true,
)
}
}
}
-/**
- * Renders the "standard" layout of the bouncer, where the bouncer is rendered on its own (no user
- * switcher UI) and laid out vertically, centered horizontally.
- *
- * If [isSplit] is `true`, the top and bottom parts of the bouncer are split such that they don't
- * render across the location of the fold hardware when the device is fully or part-way unfolded
- * with the fold hinge in a horizontal position.
- *
- * If [outputOnly] is `true`, only the "output" part of the UI is shown (where the entered PIN
- * "shapes" appear), if `false`, the entire UI is shown, including the area where the user can enter
- * their PIN or pattern.
- */
@Composable
-private fun SceneScope.FoldSplittable(
- viewModel: BouncerViewModel,
- dialogFactory: BouncerDialogFactory,
- layout: BouncerSceneLayout,
- outputOnly: Boolean,
+private fun SceneScope.FoldableScene(
+ aboveFold: @Composable BoxScope.() -> Unit,
+ belowFold: @Composable BoxScope.() -> Unit,
isSplit: Boolean,
modifier: Modifier = Modifier,
) {
- val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState()
- val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState()
- var dialog: Dialog? by remember { mutableStateOf(null) }
- val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState()
val splitRatio =
LocalContext.current.resources.getFloat(
R.dimen.motion_layout_half_fold_bouncer_height_ratio
)
- Column(modifier = modifier.padding(horizontal = 32.dp)) {
+ Column(
+ modifier = modifier.fillMaxHeight(),
+ ) {
// Content above the fold, when split on a foldable device in a "table top" posture:
Box(
modifier =
Modifier.element(SceneElements.AboveFold)
- .fillMaxWidth()
.then(
if (isSplit) {
Modifier.weight(splitRatio)
- } else if (outputOnly) {
- Modifier.fillMaxHeight()
} else {
Modifier
}
),
) {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- modifier = Modifier.fillMaxWidth().padding(top = layout.topPadding),
- ) {
- Crossfade(
- targetState = message,
- label = "Bouncer message",
- animationSpec = if (message.isUpdateAnimated) tween() else snap(),
- ) { message ->
- Text(
- text = message.text,
- color = MaterialTheme.colorScheme.onSurface,
- style = MaterialTheme.typography.bodyLarge,
- )
- }
-
- if (!outputOnly) {
- Spacer(Modifier.height(layout.spacingBetweenMessageAndEnteredInput))
-
- UserInputArea(
- viewModel = viewModel,
- visibility = UserInputAreaVisibility.OUTPUT_ONLY,
- layout = layout,
- )
- }
- }
-
- if (outputOnly) {
- UserInputArea(
- viewModel = viewModel,
- visibility = UserInputAreaVisibility.OUTPUT_ONLY,
- layout = layout,
- modifier = Modifier.align(Alignment.Center),
- )
- }
+ aboveFold()
}
// Content below the fold, when split on a foldable device in a "table top" posture:
Box(
modifier =
Modifier.element(SceneElements.BelowFold)
- .fillMaxWidth()
.weight(
if (isSplit) {
1 - splitRatio
@@ -277,73 +545,40 @@
}
),
) {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- modifier = Modifier.fillMaxSize()
- ) {
- if (!outputOnly) {
- Box(Modifier.weight(1f)) {
- UserInputArea(
- viewModel = viewModel,
- visibility = UserInputAreaVisibility.INPUT_ONLY,
- layout = layout,
- modifier = Modifier.align(Alignment.BottomCenter),
- )
- }
- }
-
- Spacer(Modifier.height(48.dp))
-
- val actionButtonModifier = Modifier.height(56.dp)
-
- actionButton.let { actionButtonViewModel ->
- if (actionButtonViewModel != null) {
- BouncerActionButton(
- viewModel = actionButtonViewModel,
- modifier = actionButtonModifier,
- )
- } else {
- Spacer(modifier = actionButtonModifier)
- }
- }
-
- Spacer(Modifier.height(layout.bottomPadding))
- }
- }
-
- if (dialogMessage != null) {
- if (dialog == null) {
- dialog =
- dialogFactory().apply {
- setMessage(dialogMessage)
- setButton(
- DialogInterface.BUTTON_NEUTRAL,
- context.getString(R.string.ok),
- ) { _, _ ->
- viewModel.onThrottlingDialogDismissed()
- }
- setCancelable(false)
- setCanceledOnTouchOutside(false)
- show()
- }
- }
- } else {
- dialog?.dismiss()
- dialog = null
+ belowFold()
}
}
}
+@Composable
+private fun StatusMessage(
+ viewModel: BouncerViewModel,
+ modifier: Modifier = Modifier,
+) {
+ val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState()
+
+ Crossfade(
+ targetState = message,
+ label = "Bouncer message",
+ animationSpec = if (message.isUpdateAnimated) tween() else snap(),
+ modifier = modifier,
+ ) {
+ Text(
+ text = it.text,
+ color = MaterialTheme.colorScheme.onSurface,
+ style = MaterialTheme.typography.bodyLarge,
+ )
+ }
+}
+
/**
- * Renders the user input area, where the user interacts with the UI to enter their credentials.
+ * Renders the user output area, where the user sees what they entered.
*
- * For example, this can be the pattern input area, the password text box, or pin pad.
+ * For example, this can be the PIN shapes or password text field.
*/
@Composable
-private fun UserInputArea(
+private fun OutputArea(
viewModel: BouncerViewModel,
- visibility: UserInputAreaVisibility,
- layout: BouncerSceneLayout,
modifier: Modifier = Modifier,
) {
val authMethodViewModel: AuthMethodBouncerViewModel? by
@@ -351,66 +586,115 @@
when (val nonNullViewModel = authMethodViewModel) {
is PinBouncerViewModel ->
- when (visibility) {
- UserInputAreaVisibility.OUTPUT_ONLY ->
- PinInputDisplay(
- viewModel = nonNullViewModel,
- modifier = modifier,
- )
- UserInputAreaVisibility.INPUT_ONLY ->
- PinPad(
- viewModel = nonNullViewModel,
- layout = layout,
- modifier = modifier,
- )
- }
+ PinInputDisplay(
+ viewModel = nonNullViewModel,
+ modifier = modifier,
+ )
is PasswordBouncerViewModel ->
- if (visibility == UserInputAreaVisibility.INPUT_ONLY) {
- PasswordBouncer(
- viewModel = nonNullViewModel,
- modifier = modifier,
- )
- }
- is PatternBouncerViewModel ->
- if (visibility == UserInputAreaVisibility.INPUT_ONLY) {
- PatternBouncer(
- viewModel = nonNullViewModel,
- layout = layout,
- modifier = modifier.aspectRatio(1f, matchHeightConstraintsFirst = false),
- )
- }
+ PasswordBouncer(
+ viewModel = nonNullViewModel,
+ modifier = modifier,
+ )
else -> Unit
}
}
/**
- * Renders the action button on the bouncer, which triggers either Return to Call or Emergency Call.
+ * Renders the user input area, where the user enters their credentials.
+ *
+ * For example, this can be the pattern input area or the PIN pad.
*/
-@OptIn(ExperimentalFoundationApi::class)
@Composable
-private fun BouncerActionButton(
- viewModel: BouncerActionButtonModel,
+private fun InputArea(
+ viewModel: BouncerViewModel,
+ pinButtonRowVerticalSpacing: Dp,
+ centerPatternDotsVertically: Boolean,
modifier: Modifier = Modifier,
) {
- Button(
- onClick = viewModel.onClick,
- modifier =
- modifier.thenIf(viewModel.onLongClick != null) {
- Modifier.combinedClickable(
- onClick = viewModel.onClick,
- onLongClick = viewModel.onLongClick,
+ val authMethodViewModel: AuthMethodBouncerViewModel? by
+ viewModel.authMethodViewModel.collectAsState()
+
+ when (val nonNullViewModel = authMethodViewModel) {
+ is PinBouncerViewModel -> {
+ PinPad(
+ viewModel = nonNullViewModel,
+ verticalSpacing = pinButtonRowVerticalSpacing,
+ modifier = modifier,
+ )
+ }
+ is PatternBouncerViewModel -> {
+ PatternBouncer(
+ viewModel = nonNullViewModel,
+ centerDotsVertically = centerPatternDotsVertically,
+ modifier = modifier,
+ )
+ }
+ else -> Unit
+ }
+}
+
+@Composable
+private fun ActionArea(
+ viewModel: BouncerViewModel,
+ modifier: Modifier = Modifier,
+) {
+ val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState()
+
+ actionButton?.let { actionButtonViewModel ->
+ Box(
+ modifier = modifier,
+ ) {
+ Button(
+ onClick = actionButtonViewModel.onClick,
+ modifier =
+ Modifier.height(56.dp).thenIf(actionButtonViewModel.onLongClick != null) {
+ Modifier.combinedClickable(
+ onClick = actionButtonViewModel.onClick,
+ onLongClick = actionButtonViewModel.onLongClick,
+ )
+ },
+ colors =
+ ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+ contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
+ ),
+ ) {
+ Text(
+ text = actionButtonViewModel.label,
+ style = MaterialTheme.typography.bodyMedium,
)
- },
- colors =
- ButtonDefaults.buttonColors(
- containerColor = MaterialTheme.colorScheme.tertiaryContainer,
- contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
- ),
- ) {
- Text(
- text = viewModel.label,
- style = MaterialTheme.typography.bodyMedium,
- )
+ }
+ }
+ }
+}
+
+@Composable
+private fun Dialog(
+ viewModel: BouncerViewModel,
+ dialogFactory: BouncerDialogFactory,
+) {
+ val dialogMessage: String? by viewModel.dialogMessage.collectAsState()
+ var dialog: Dialog? by remember { mutableStateOf(null) }
+
+ if (dialogMessage != null) {
+ if (dialog == null) {
+ dialog =
+ dialogFactory().apply {
+ setMessage(dialogMessage)
+ setButton(
+ DialogInterface.BUTTON_NEUTRAL,
+ context.getString(R.string.ok),
+ ) { _, _ ->
+ viewModel.onDialogDismissed()
+ }
+ setCancelable(false)
+ setCanceledOnTouchOutside(false)
+ show()
+ }
+ }
+ } else {
+ dialog?.dismiss()
+ dialog = null
}
}
@@ -420,6 +704,14 @@
viewModel: BouncerViewModel,
modifier: Modifier = Modifier,
) {
+ if (!viewModel.isUserSwitcherVisible) {
+ // Take up the same space as the user switcher normally would, but with nothing inside it.
+ Box(
+ modifier = modifier,
+ )
+ return
+ }
+
val selectedUserImage by viewModel.selectedUserImage.collectAsState(null)
val dropdownItems by viewModel.userSwitcherDropdown.collectAsState(emptyList())
@@ -539,195 +831,10 @@
}
}
-/**
- * Renders the bouncer UI in split mode, with half on one side and half on the other side, swappable
- * by double-tapping on the side.
- */
-@Composable
-private fun SplitLayout(
- viewModel: BouncerViewModel,
- dialogFactory: BouncerDialogFactory,
- modifier: Modifier = Modifier,
-) {
- SwappableLayout(
- startContent = { startContentModifier ->
- StandardLayout(
- viewModel = viewModel,
- dialogFactory = dialogFactory,
- layout = BouncerSceneLayout.SPLIT,
- outputOnly = true,
- modifier = startContentModifier,
- )
- },
- endContent = { endContentModifier ->
- UserInputArea(
- viewModel = viewModel,
- visibility = UserInputAreaVisibility.INPUT_ONLY,
- layout = BouncerSceneLayout.SPLIT,
- modifier = endContentModifier,
- )
- },
- layout = BouncerSceneLayout.SPLIT,
- modifier = modifier,
- )
-}
-
-/**
- * Arranges the given two contents side-by-side, supporting a double tap anywhere on the background
- * to flip their positions.
- */
-@Composable
-private fun SwappableLayout(
- startContent: @Composable (Modifier) -> Unit,
- endContent: @Composable (Modifier) -> Unit,
- layout: BouncerSceneLayout,
- modifier: Modifier = Modifier,
-) {
- val layoutDirection = LocalLayoutDirection.current
- val isLeftToRight = layoutDirection == LayoutDirection.Ltr
- val (isSwapped, setSwapped) = rememberSaveable(isLeftToRight) { mutableStateOf(!isLeftToRight) }
-
- Row(
- modifier =
- modifier.pointerInput(Unit) {
- detectTapGestures(
- onDoubleTap = { offset ->
- // Depending on where the user double tapped, switch the elements such that
- // the endContent is closer to the side that was double tapped.
- setSwapped(offset.x < size.width / 2)
- }
- )
- },
- ) {
- val animatedOffset by
- animateFloatAsState(
- targetValue =
- if (!isSwapped) {
- // When startContent is first, both elements have their natural placement so
- // they are not offset in any way.
- 0f
- } else if (isLeftToRight) {
- // Since startContent is not first, the elements have to be swapped
- // horizontally. In the case of LTR locales, this means pushing startContent
- // to the right, hence the positive number.
- 1f
- } else {
- // Since startContent is not first, the elements have to be swapped
- // horizontally. In the case of RTL locales, this means pushing startContent
- // to the left, hence the negative number.
- -1f
- },
- label = "offset",
- )
-
- startContent(
- Modifier.fillMaxHeight().weight(1f).graphicsLayer {
- translationX = size.width * animatedOffset
- alpha = animatedAlpha(animatedOffset)
- }
- )
-
- Box(
- modifier =
- Modifier.fillMaxHeight().weight(1f).graphicsLayer {
- // A negative sign is used to make sure this is offset in the direction that's
- // opposite of the direction that the user switcher is pushed in.
- translationX = -size.width * animatedOffset
- alpha = animatedAlpha(animatedOffset)
- }
- ) {
- endContent(Modifier.align(layout.swappableEndContentAlignment).widthIn(max = 400.dp))
- }
- }
-}
-
-/**
- * Arranges the bouncer contents and user switcher contents side-by-side, supporting a double tap
- * anywhere on the background to flip their positions.
- *
- * In situations when [isUserSwitcherVisible] is `false`, one of two things may happen: either the
- * UI for the bouncer will be shown on its own, taking up one side, with the other side just being
- * empty space or, if that kind of "stand-alone side-by-side" isn't supported, the standard
- * rendering of the bouncer will be used instead of the side-by-side layout.
- */
-@Composable
-private fun SideBySideLayout(
- viewModel: BouncerViewModel,
- dialogFactory: BouncerDialogFactory,
- isUserSwitcherVisible: Boolean,
- modifier: Modifier = Modifier,
-) {
- SwappableLayout(
- startContent = { startContentModifier ->
- if (isUserSwitcherVisible) {
- UserSwitcher(
- viewModel = viewModel,
- modifier = startContentModifier,
- )
- } else {
- Box(
- modifier = startContentModifier,
- )
- }
- },
- endContent = { endContentModifier ->
- StandardLayout(
- viewModel = viewModel,
- dialogFactory = dialogFactory,
- layout = BouncerSceneLayout.SIDE_BY_SIDE,
- modifier = endContentModifier,
- )
- },
- layout = BouncerSceneLayout.SIDE_BY_SIDE,
- modifier = modifier,
- )
-}
-
-/** Arranges the bouncer contents and user switcher contents one on top of the other, vertically. */
-@Composable
-private fun StackedLayout(
- viewModel: BouncerViewModel,
- dialogFactory: BouncerDialogFactory,
- isUserSwitcherVisible: Boolean,
- modifier: Modifier = Modifier,
-) {
- Column(
- modifier = modifier,
- ) {
- if (isUserSwitcherVisible) {
- UserSwitcher(
- viewModel = viewModel,
- modifier = Modifier.fillMaxWidth().weight(1f),
- )
- }
-
- StandardLayout(
- viewModel = viewModel,
- dialogFactory = dialogFactory,
- layout = BouncerSceneLayout.STACKED,
- modifier = Modifier.fillMaxWidth().weight(1f),
- )
- }
-}
-
interface BouncerDialogFactory {
operator fun invoke(): AlertDialog
}
-/** Enumerates all supported user-input area visibilities. */
-private enum class UserInputAreaVisibility {
- /**
- * Only the area where the user enters the input is shown; the area where the input is reflected
- * back to the user is not shown.
- */
- INPUT_ONLY,
- /**
- * Only the area where the input is reflected back to the user is shown; the area where the
- * input is entered by the user is not shown.
- */
- OUTPUT_ONLY,
-}
-
/**
* Calculates an alpha for the user switcher and bouncer such that it's at `1` when the offset of
* the two reaches a stopping point but `0` in the middle of the transition.
@@ -774,48 +881,3 @@
private val SceneTransitions = transitions {
from(SceneKeys.ContiguousSceneKey, to = SceneKeys.SplitSceneKey) { spec = tween() }
}
-
-/** Whether a more compact size should be used for various spacing dimensions. */
-internal val BouncerSceneLayout.isUseCompactSize: Boolean
- get() =
- when (this) {
- BouncerSceneLayout.SIDE_BY_SIDE -> true
- BouncerSceneLayout.SPLIT -> true
- else -> false
- }
-
-/** Amount of space to place between the message and the entered input UI elements, in dips. */
-private val BouncerSceneLayout.spacingBetweenMessageAndEnteredInput: Dp
- get() =
- when {
- this == BouncerSceneLayout.STACKED -> 24.dp
- isUseCompactSize -> 96.dp
- else -> 128.dp
- }
-
-/** Amount of space to place above the topmost UI element, in dips. */
-private val BouncerSceneLayout.topPadding: Dp
- get() =
- if (this == BouncerSceneLayout.SPLIT) {
- 40.dp
- } else {
- 92.dp
- }
-
-/** Amount of space to place below the bottommost UI element, in dips. */
-private val BouncerSceneLayout.bottomPadding: Dp
- get() =
- if (this == BouncerSceneLayout.SPLIT) {
- 40.dp
- } else {
- 48.dp
- }
-
-/** The in-a-box alignment for the content on the "end" side of a swappable layout. */
-private val BouncerSceneLayout.swappableEndContentAlignment: Alignment
- get() =
- if (this == BouncerSceneLayout.SPLIT) {
- Alignment.Center
- } else {
- Alignment.BottomCenter
- }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt
index 08b7559..1c3d93c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt
@@ -26,8 +26,8 @@
/**
* Returns the [BouncerSceneLayout] that should be used by the bouncer scene. If
- * [isSideBySideSupported] is `false`, then [BouncerSceneLayout.SIDE_BY_SIDE] is replaced by
- * [BouncerSceneLayout.STANDARD].
+ * [isSideBySideSupported] is `false`, then [BouncerSceneLayout.BESIDE_USER_SWITCHER] is replaced by
+ * [BouncerSceneLayout.STANDARD_BOUNCER].
*/
@Composable
fun calculateLayout(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
index 2799959..0960811 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
@@ -17,7 +17,6 @@
package com.android.systemui.bouncer.ui.composable
import android.view.ViewTreeObserver
-import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.LocalTextStyle
@@ -31,7 +30,6 @@
import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.focus.FocusRequester
@@ -81,42 +79,38 @@
}
}
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- modifier = modifier,
- ) {
- val color = MaterialTheme.colorScheme.onSurfaceVariant
- val lineWidthPx = with(LocalDensity.current) { 2.dp.toPx() }
+ val color = MaterialTheme.colorScheme.onSurfaceVariant
+ val lineWidthPx = with(LocalDensity.current) { 2.dp.toPx() }
- TextField(
- value = password,
- onValueChange = viewModel::onPasswordInputChanged,
- enabled = isInputEnabled,
- visualTransformation = PasswordVisualTransformation(),
- singleLine = true,
- textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
- keyboardOptions =
- KeyboardOptions(
- keyboardType = KeyboardType.Password,
- imeAction = ImeAction.Done,
- ),
- keyboardActions =
- KeyboardActions(
- onDone = { viewModel.onAuthenticateKeyPressed() },
- ),
- modifier =
- Modifier.focusRequester(focusRequester)
- .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) }
- .drawBehind {
- drawLine(
- color = color,
- start = Offset(x = 0f, y = size.height - lineWidthPx),
- end = Offset(size.width, y = size.height - lineWidthPx),
- strokeWidth = lineWidthPx,
- )
- },
- )
- }
+ TextField(
+ value = password,
+ onValueChange = viewModel::onPasswordInputChanged,
+ enabled = isInputEnabled,
+ visualTransformation = PasswordVisualTransformation(),
+ singleLine = true,
+ textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
+ keyboardOptions =
+ KeyboardOptions(
+ keyboardType = KeyboardType.Password,
+ imeAction = ImeAction.Done,
+ ),
+ keyboardActions =
+ KeyboardActions(
+ onDone = { viewModel.onAuthenticateKeyPressed() },
+ ),
+ modifier =
+ modifier
+ .focusRequester(focusRequester)
+ .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) }
+ .drawBehind {
+ drawLine(
+ color = color,
+ start = Offset(x = 0f, y = size.height - lineWidthPx),
+ end = Offset(size.width, y = size.height - lineWidthPx),
+ strokeWidth = lineWidthPx,
+ )
+ },
+ )
}
/** Returns a [State] with `true` when the IME/keyboard is visible and `false` when it's not. */
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index a4b1955..0a5f5d2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -24,6 +24,8 @@
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.detectDragGestures
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@@ -48,7 +50,6 @@
import com.android.compose.animation.Easings
import com.android.compose.modifiers.thenIf
import com.android.internal.R
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PatternDotViewModel
import kotlin.math.min
@@ -61,11 +62,14 @@
* UI for the input part of a pattern-requiring version of the bouncer.
*
* The user can press, hold, and drag their pointer to select dots along a grid of dots.
+ *
+ * If [centerDotsVertically] is `true`, the dots should be centered along the axis of interest; if
+ * `false`, the dots will be pushed towards the end/bottom of the axis.
*/
@Composable
internal fun PatternBouncer(
viewModel: PatternBouncerViewModel,
- layout: BouncerSceneLayout,
+ centerDotsVertically: Boolean,
modifier: Modifier = Modifier,
) {
DisposableEffect(Unit) {
@@ -197,6 +201,14 @@
Canvas(
modifier
+ // Because the width also includes spacing to the left and right of the leftmost and
+ // rightmost dots in the grid and because UX mocks specify the width without that
+ // spacing, the actual width needs to be defined slightly bigger than the UX mock width.
+ .width((262 * colCount / 2).dp)
+ // Because the height also includes spacing above and below the topmost and bottommost
+ // dots in the grid and because UX mocks specify the height without that spacing, the
+ // actual height needs to be defined slightly bigger than the UX mock height.
+ .height((262 * rowCount / 2).dp)
// Need to clip to bounds to make sure that the lines don't follow the input pointer
// when it leaves the bounds of the dot grid.
.clipToBounds()
@@ -260,7 +272,7 @@
availableSize = containerSize.height,
spacingPerDot = spacing,
dotCount = rowCount,
- isCentered = layout.isCenteredVertically,
+ isCentered = centerDotsVertically,
)
offset = Offset(horizontalOffset, verticalOffset)
scale = (colCount * spacing) / containerSize.width
@@ -423,10 +435,6 @@
}
}
-/** Whether the UI should be centered vertically. */
-private val BouncerSceneLayout.isCenteredVertically: Boolean
- get() = this == BouncerSceneLayout.SPLIT
-
private const val DOT_DIAMETER_DP = 16
private const val SELECTED_DOT_DIAMETER_DP = 24
private const val SELECTED_DOT_REACTION_ANIMATION_DURATION_MS = 83
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
index 8f5d9f4..f505b90 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
@@ -52,7 +52,6 @@
import com.android.compose.animation.Easings
import com.android.compose.grid.VerticalGrid
import com.android.compose.modifiers.thenIf
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
import com.android.systemui.bouncer.ui.viewmodel.ActionButtonAppearance
import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
import com.android.systemui.common.shared.model.ContentDescription
@@ -70,7 +69,7 @@
@Composable
fun PinPad(
viewModel: PinBouncerViewModel,
- layout: BouncerSceneLayout,
+ verticalSpacing: Dp,
modifier: Modifier = Modifier,
) {
DisposableEffect(Unit) {
@@ -96,8 +95,8 @@
VerticalGrid(
columns = columns,
- verticalSpacing = layout.verticalSpacing,
- horizontalSpacing = calculateHorizontalSpacingBetweenColumns(layout.gridWidth),
+ verticalSpacing = verticalSpacing,
+ horizontalSpacing = calculateHorizontalSpacingBetweenColumns(gridWidth = 300.dp),
modifier = modifier,
) {
repeat(9) { index ->
@@ -355,14 +354,6 @@
return (gridWidth - (pinButtonMaxSize * columns)) / (columns - 1)
}
-/** The width of the grid of PIN pad buttons, in dips. */
-private val BouncerSceneLayout.gridWidth: Dp
- get() = if (isUseCompactSize) 292.dp else 300.dp
-
-/** The spacing between rows of PIN pad buttons, in dips. */
-private val BouncerSceneLayout.verticalSpacing: Dp
- get() = if (isUseCompactSize) 8.dp else 12.dp
-
/** Number of columns in the PIN pad grid. */
private const val columns = 3
/** Maximum size (width and height) of each PIN pad button. */
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index c49c197..12f1b30 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -27,11 +27,13 @@
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onPlaced
@@ -89,13 +91,18 @@
isScrimVisible: Boolean,
modifier: Modifier = Modifier,
) {
+ val cornerRadius by viewModel.cornerRadiusDp.collectAsState()
+
Box(modifier = modifier) {
if (isScrimVisible) {
Box(
modifier =
Modifier.element(Notifications.Elements.NotificationScrim)
.fillMaxSize()
- .clip(RoundedCornerShape(32.dp))
+ .graphicsLayer {
+ shape = RoundedCornerShape(cornerRadius.dp)
+ clip = true
+ }
.background(MaterialTheme.colorScheme.surface)
)
}
@@ -167,7 +174,9 @@
}
val boundsInWindow = coordinates.boundsInWindow()
viewModel.onBoundsChanged(
+ left = boundsInWindow.left,
top = boundsInWindow.top,
+ right = boundsInWindow.right,
bottom = boundsInWindow.bottom,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 543b291..695d888 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -465,29 +465,6 @@
}
@Test
- fun showNextSecurityScreenOrFinish_setsSecurityScreenToPinAfterSimPinUnlock() {
- // GIVEN the current security method is SimPin
- whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
- whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
- .thenReturn(false)
- underTest.showSecurityScreen(SecurityMode.SimPin)
-
- // WHEN a request is made from the SimPin screens to show the next security method
- whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.PIN)
- underTest.showNextSecurityScreenOrFinish(
- /* authenticated= */ true,
- TARGET_USER_ID,
- /* bypassSecondaryLockScreen= */ true,
- SecurityMode.SimPin
- )
-
- // THEN the next security method of PIN is set, and the keyguard is not marked as done
- verify(viewMediatorCallback, never()).keyguardDonePending(anyInt())
- verify(viewMediatorCallback, never()).keyguardDone(anyInt())
- Truth.assertThat(underTest.currentSecurityMode).isEqualTo(SecurityMode.PIN)
- }
-
- @Test
fun showNextSecurityScreenOrFinish_DeviceNotSecure() {
// GIVEN the current security method is SimPin
whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
@@ -578,6 +555,57 @@
}
@Test
+ fun showNextSecurityScreenOrFinish_SimPin_Password() {
+ // GIVEN the current security method is SimPin
+ whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
+ whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
+ .thenReturn(false)
+ underTest.showSecurityScreen(SecurityMode.SimPin)
+
+ // WHEN a request is made from the SimPin screens to show the next security method
+ whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID))
+ .thenReturn(SecurityMode.Password)
+ // WHEN security method is SWIPE
+ whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
+ whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(false)
+ underTest.showNextSecurityScreenOrFinish(
+ /* authenticated= */ true,
+ TARGET_USER_ID,
+ /* bypassSecondaryLockScreen= */ true,
+ SecurityMode.SimPin
+ )
+
+ // THEN we will not show the password screen.
+ verify(viewFlipperController, never())
+ .getSecurityView(eq(SecurityMode.Password), any(), any())
+ }
+
+ @Test
+ fun showNextSecurityScreenOrFinish_SimPin_SimPin() {
+ // GIVEN the current security method is SimPin
+ whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
+ whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
+ .thenReturn(false)
+ underTest.showSecurityScreen(SecurityMode.SimPin)
+
+ // WHEN a request is made from the SimPin screens to show the next security method
+ whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID))
+ .thenReturn(SecurityMode.SimPin)
+ // WHEN security method is SWIPE
+ whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
+ whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(false)
+ underTest.showNextSecurityScreenOrFinish(
+ /* authenticated= */ true,
+ TARGET_USER_ID,
+ /* bypassSecondaryLockScreen= */ true,
+ SecurityMode.SimPin
+ )
+
+ // THEN we will not show the password screen.
+ verify(viewFlipperController).getSecurityView(eq(SecurityMode.SimPin), any(), any())
+ }
+
+ @Test
fun onSwipeUp_forwardsItToFaceAuthInteractor() {
val registeredSwipeListener = registeredSwipeListener
setupGetSecurityView(SecurityMode.Password)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index 94c3bde..84d73543 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -34,11 +34,14 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@@ -63,6 +66,8 @@
@Mock
private lateinit var keyguardMessageAreaController:
KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+ private val updateMonitorCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
@Before
fun setup() {
@@ -95,6 +100,9 @@
mSelectedUserInteractor
)
underTest.init()
+ underTest.onResume(0)
+ verify(keyguardUpdateMonitor)
+ .registerCallback(updateMonitorCallbackArgumentCaptor.capture())
}
@Test
@@ -111,6 +119,7 @@
@Test
fun onResume() {
+ reset(keyguardUpdateMonitor)
underTest.onResume(KeyguardSecurityView.VIEW_REVEALED)
verify(keyguardUpdateMonitor)
.registerCallback(any(KeyguardUpdateMonitorCallback::class.java))
@@ -137,4 +146,22 @@
underTest.resetState()
verify(keyguardMessageAreaController).setMessage("")
}
+
+ @Test
+ fun onSimStateChangedFromPinToPuk_showsCurrentSecurityScreen() {
+ updateMonitorCallbackArgumentCaptor.value.onSimStateChanged(
+ /* subId= */ 0,
+ /* slotId= */ 0,
+ TelephonyManager.SIM_STATE_PIN_REQUIRED
+ )
+ verify(keyguardSecurityCallback, never()).showCurrentSecurityScreen()
+
+ updateMonitorCallbackArgumentCaptor.value.onSimStateChanged(
+ /* subId= */ 0,
+ /* slotId= */ 0,
+ TelephonyManager.SIM_STATE_PUK_REQUIRED
+ )
+
+ verify(keyguardSecurityCallback).showCurrentSecurityScreen()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index 2a02164..08cd7ed 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -21,9 +21,9 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
-import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.scene.SceneTestUtils
import com.google.common.truth.Truth.assertThat
@@ -76,12 +76,13 @@
@Test
fun authenticate_withCorrectPin_succeeds() =
testScope.runTest {
- val throttling by collectLastValue(underTest.throttling)
+ val lockout by collectLastValue(underTest.lockout)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
.isEqualTo(AuthenticationResult.SUCCEEDED)
- assertThat(throttling).isNull()
+ assertThat(lockout).isNull()
+ assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
}
@Test
@@ -129,14 +130,15 @@
@Test
fun authenticate_withCorrectPassword_succeeds() =
testScope.runTest {
- val throttling by collectLastValue(underTest.throttling)
+ val lockout by collectLastValue(underTest.lockout)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
assertThat(underTest.authenticate("password".toList()))
.isEqualTo(AuthenticationResult.SUCCEEDED)
- assertThat(throttling).isNull()
+ assertThat(lockout).isNull()
+ assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
}
@Test
@@ -185,7 +187,7 @@
fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNull() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- val throttling by collectLastValue(underTest.throttling)
+ val lockout by collectLastValue(underTest.lockout)
utils.authenticationRepository.apply {
setAuthenticationMethod(AuthenticationMethodModel.Pin)
setAutoConfirmFeatureEnabled(true)
@@ -201,7 +203,8 @@
)
)
.isEqualTo(AuthenticationResult.SKIPPED)
- assertThat(throttling).isNull()
+ assertThat(lockout).isNull()
+ assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
}
@Test
@@ -262,7 +265,7 @@
}
@Test
- fun tryAutoConfirm_withAutoConfirmCorrectPinButDuringThrottling_returnsNull() =
+ fun tryAutoConfirm_withAutoConfirmCorrectPinButDuringLockout_returnsNull() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked)
@@ -270,7 +273,7 @@
utils.authenticationRepository.apply {
setAuthenticationMethod(AuthenticationMethodModel.Pin)
setAutoConfirmFeatureEnabled(true)
- setThrottleDuration(42)
+ setLockoutDuration(42)
}
val authResult =
@@ -331,29 +334,30 @@
}
@Test
- fun isAutoConfirmEnabled_featureEnabledButDisabledByThrottling() =
+ fun isAutoConfirmEnabled_featureEnabledButDisabledByLockout() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- val throttling by collectLastValue(underTest.throttling)
+ val lockout by collectLastValue(underTest.lockout)
utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
// The feature is enabled.
assertThat(isAutoConfirmEnabled).isTrue()
- // Make many wrong attempts to trigger throttling.
- repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
+ // Make many wrong attempts to trigger lockout.
+ repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
}
- assertThat(throttling).isNotNull()
+ assertThat(lockout).isNotNull()
+ assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
- // Throttling disabled auto-confirm.
+ // Lockout disabled auto-confirm.
assertThat(isAutoConfirmEnabled).isFalse()
- // Move the clock forward one more second, to completely finish the throttling period:
- advanceTimeBy(FakeAuthenticationRepository.THROTTLE_DURATION_MS + 1000L)
- assertThat(throttling).isNull()
+ // Move the clock forward one more second, to completely finish the lockout period:
+ advanceTimeBy(FakeAuthenticationRepository.LOCKOUT_DURATION_MS + 1000L)
+ assertThat(lockout).isNull()
- // Auto-confirm is still disabled, because throttling occurred at least once in this
+ // Auto-confirm is still disabled, because lockout occurred at least once in this
// session.
assertThat(isAutoConfirmEnabled).isFalse()
@@ -363,68 +367,70 @@
// Auto-confirm is re-enabled.
assertThat(isAutoConfirmEnabled).isTrue()
+
+ assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
}
@Test
- fun throttling() =
+ fun lockout() =
testScope.runTest {
- val throttling by collectLastValue(underTest.throttling)
+ val lockout by collectLastValue(underTest.lockout)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
- assertThat(throttling).isNull()
+ assertThat(lockout).isNull()
- // Make many wrong attempts, but just shy of what's needed to get throttled:
- repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1) {
+ // Make many wrong attempts, but just shy of what's needed to get locked out:
+ repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
- assertThat(throttling).isNull()
+ assertThat(lockout).isNull()
}
- // Make one more wrong attempt, leading to throttling:
+ // Make one more wrong attempt, leading to lockout:
underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
- assertThat(throttling)
+ assertThat(lockout)
.isEqualTo(
- AuthenticationThrottlingModel(
+ AuthenticationLockoutModel(
failedAttemptCount =
- FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
- remainingSeconds = FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS,
+ FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT,
+ remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS,
)
)
+ assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
- // Correct PIN, but throttled, so doesn't attempt it:
+ // Correct PIN, but locked out, so doesn't attempt it:
assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
.isEqualTo(AuthenticationResult.SKIPPED)
- assertThat(throttling)
+ assertThat(lockout)
.isEqualTo(
- AuthenticationThrottlingModel(
+ AuthenticationLockoutModel(
failedAttemptCount =
- FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
- remainingSeconds = FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS,
+ FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT,
+ remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS,
)
)
- // Move the clock forward to ALMOST skip the throttling, leaving one second to go:
- val throttleTimeoutSec = FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS
- repeat(FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS - 1) { time ->
+ // Move the clock forward to ALMOST skip the lockout, leaving one second to go:
+ val lockoutTimeoutSec = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS
+ repeat(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS - 1) { time ->
advanceTimeBy(1000)
- assertThat(throttling)
+ assertThat(lockout)
.isEqualTo(
- AuthenticationThrottlingModel(
+ AuthenticationLockoutModel(
failedAttemptCount =
- FakeAuthenticationRepository
- .MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
- remainingSeconds = throttleTimeoutSec - (time + 1),
+ FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT,
+ remainingSeconds = lockoutTimeoutSec - (time + 1),
)
)
}
- // Move the clock forward one more second, to completely finish the throttling period:
+ // Move the clock forward one more second, to completely finish the lockout period:
advanceTimeBy(1000)
- assertThat(throttling).isNull()
+ assertThat(lockout).isNull()
- // Correct PIN and no longer throttled so unlocks successfully:
+ // Correct PIN and no longer locked out so unlocks successfully:
assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
.isEqualTo(AuthenticationResult.SUCCEEDED)
- assertThat(throttling).isNull()
+ assertThat(lockout).isNull()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index cbb772f..0ab596c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -21,6 +21,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityModel
+import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF
+import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl
@@ -56,10 +58,12 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -539,4 +543,77 @@
.onDozeAmountChanged(eq(0f), eq(0f), eq(UdfpsKeyguardViewLegacy.ANIMATION_NONE))
job.cancel()
}
+
+ @Test
+ fun cancelledLockscreenToAod_dozeAmountNotUpdatedToZero() =
+ testScope.runTest {
+ // GIVEN view is attached
+ mController.onViewAttached()
+ Mockito.reset(mView)
+
+ val job = mController.listenForLockscreenAodTransitions(this)
+ // WHEN lockscreen to aod transition is cancelled
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = 1f,
+ transitionState = TransitionState.CANCELED
+ )
+ )
+ runCurrent()
+
+ // THEN doze amount is NOT updated to zero
+ verify(mView, never()).onDozeAmountChanged(eq(0f), eq(0f), anyInt())
+ job.cancel()
+ }
+
+ @Test
+ fun dreamingToAod_dozeAmountChanged() =
+ testScope.runTest {
+ // GIVEN view is attached
+ mController.onViewAttached()
+ Mockito.reset(mView)
+
+ val job = mController.listenForDreamingToAodTransitions(this)
+ // WHEN dreaming to aod transition in progress
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.AOD,
+ value = .3f,
+ transitionState = TransitionState.RUNNING
+ )
+ )
+ runCurrent()
+
+ // THEN doze amount is updated to
+ verify(mView).onDozeAmountChanged(eq(.3f), eq(.3f), eq(ANIMATE_APPEAR_ON_SCREEN_OFF))
+ job.cancel()
+ }
+
+ @Test
+ fun alternateBouncerToAod_dozeAmountChanged() =
+ testScope.runTest {
+ // GIVEN view is attached
+ mController.onViewAttached()
+ Mockito.reset(mView)
+
+ val job = mController.listenForAlternateBouncerToAodTransitions(this)
+ // WHEN alternate bouncer to aod transition in progress
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.AOD,
+ value = .3f,
+ transitionState = TransitionState.RUNNING
+ )
+ )
+ runCurrent()
+
+ // THEN doze amount is updated to
+ verify(mView)
+ .onDozeAmountChanged(eq(.3f), eq(.3f), eq(ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN))
+ job.cancel()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 3e3a1a9..9b1df7c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -21,9 +21,9 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.domain.interactor.AuthenticationResult
+import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
-import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
import com.android.systemui.res.R
@@ -246,46 +246,42 @@
}
@Test
- fun throttling() =
+ fun lockout() =
testScope.runTest {
- val throttling by collectLastValue(underTest.throttling)
+ val lockout by collectLastValue(underTest.lockout)
val message by collectLastValue(underTest.message)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- assertThat(throttling).isNull()
- repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { times ->
+ assertThat(lockout).isNull()
+ repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { times ->
// Wrong PIN.
assertThat(underTest.authenticate(listOf(6, 7, 8, 9)))
.isEqualTo(AuthenticationResult.FAILED)
- if (
- times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1
- ) {
+ if (times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
}
}
- assertThat(throttling)
+ assertThat(lockout)
.isEqualTo(
- AuthenticationThrottlingModel(
+ AuthenticationLockoutModel(
failedAttemptCount =
- FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
- remainingSeconds = FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS,
+ FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT,
+ remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS,
)
)
assertTryAgainMessage(
message,
- FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds
- .toInt()
+ FakeAuthenticationRepository.LOCKOUT_DURATION_MS.milliseconds.inWholeSeconds.toInt()
)
- // Correct PIN, but throttled, so doesn't change away from the bouncer scene:
+ // Correct PIN, but locked out, so doesn't change away from the bouncer scene:
assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
.isEqualTo(AuthenticationResult.SKIPPED)
assertTryAgainMessage(
message,
- FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds
- .toInt()
+ FakeAuthenticationRepository.LOCKOUT_DURATION_MS.milliseconds.inWholeSeconds.toInt()
)
- throttling?.remainingSeconds?.let { seconds ->
+ lockout?.remainingSeconds?.let { seconds ->
repeat(seconds) { time ->
advanceTimeBy(1000)
val remainingTimeSec = seconds - time - 1
@@ -295,12 +291,12 @@
}
}
assertThat(message).isEqualTo("")
- assertThat(throttling).isNull()
+ assertThat(lockout).isNull()
- // Correct PIN and no longer throttled so changes to the Gone scene:
+ // Correct PIN and no longer locked out so changes to the Gone scene:
assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
.isEqualTo(AuthenticationResult.SUCCEEDED)
- assertThat(throttling).isNull()
+ assertThat(lockout).isNull()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index 45c186d..2f0843b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -35,7 +35,6 @@
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
- private val sceneInteractor = utils.sceneInteractor()
private val bouncerInteractor =
utils.bouncerInteractor(
authenticationInteractor = utils.authenticationInteractor(),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index 2b64d8e..16a9359 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -135,17 +135,17 @@
fun message() =
testScope.runTest {
val message by collectLastValue(underTest.message)
- val throttling by collectLastValue(bouncerInteractor.throttling)
+ val lockout by collectLastValue(bouncerInteractor.lockout)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(message?.isUpdateAnimated).isTrue()
- repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
+ repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
// Wrong PIN.
bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
}
assertThat(message?.isUpdateAnimated).isFalse()
- throttling?.remainingSeconds?.let { remainingSeconds ->
+ lockout?.remainingSeconds?.let { remainingSeconds ->
advanceTimeBy(remainingSeconds.seconds.inWholeMilliseconds)
}
assertThat(message?.isUpdateAnimated).isTrue()
@@ -160,37 +160,37 @@
authViewModel?.isInputEnabled ?: emptyFlow()
}
)
- val throttling by collectLastValue(bouncerInteractor.throttling)
+ val lockout by collectLastValue(bouncerInteractor.lockout)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(isInputEnabled).isTrue()
- repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
+ repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
// Wrong PIN.
bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
}
assertThat(isInputEnabled).isFalse()
- throttling?.remainingSeconds?.let { remainingSeconds ->
+ lockout?.remainingSeconds?.let { remainingSeconds ->
advanceTimeBy(remainingSeconds.seconds.inWholeMilliseconds)
}
assertThat(isInputEnabled).isTrue()
}
@Test
- fun throttlingDialogMessage() =
+ fun dialogMessage() =
testScope.runTest {
- val throttlingDialogMessage by collectLastValue(underTest.throttlingDialogMessage)
+ val dialogMessage by collectLastValue(underTest.dialogMessage)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
+ repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
// Wrong PIN.
- assertThat(throttlingDialogMessage).isNull()
+ assertThat(dialogMessage).isNull()
bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
}
- assertThat(throttlingDialogMessage).isNotEmpty()
+ assertThat(dialogMessage).isNotEmpty()
- underTest.onThrottlingDialogDismissed()
- assertThat(throttlingDialogMessage).isNull()
+ underTest.onDialogDismissed()
+ assertThat(dialogMessage).isNull()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index a217d93..6d6baa5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -19,8 +19,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.res.R
@@ -243,12 +243,12 @@
}
@Test
- fun onImeVisibilityChanged_falseAfterTrue_whileThrottling_doesNothing() =
+ fun onImeVisibilityChanged_falseAfterTrue_whileLockedOut_doesNothing() =
testScope.runTest {
val events by collectValues(bouncerInteractor.onImeHiddenByUser)
assertThat(events).isEmpty()
underTest.onImeVisibilityChanged(isVisible = true)
- setThrottling(true)
+ setLockout(true)
underTest.onImeVisibilityChanged(isVisible = false)
@@ -284,11 +284,11 @@
}
@Test
- fun isTextFieldFocusRequested_focusLostWhileThrottling_staysFalse() =
+ fun isTextFieldFocusRequested_focusLostWhileLockedOut_staysFalse() =
testScope.runTest {
val isTextFieldFocusRequested by collectLastValue(underTest.isTextFieldFocusRequested)
underTest.onTextFieldFocusChanged(isFocused = true)
- setThrottling(true)
+ setLockout(true)
underTest.onTextFieldFocusChanged(isFocused = false)
@@ -296,14 +296,14 @@
}
@Test
- fun isTextFieldFocusRequested_throttlingCountdownEnds_becomesTrue() =
+ fun isTextFieldFocusRequested_lockoutCountdownEnds_becomesTrue() =
testScope.runTest {
val isTextFieldFocusRequested by collectLastValue(underTest.isTextFieldFocusRequested)
underTest.onTextFieldFocusChanged(isFocused = true)
- setThrottling(true)
+ setLockout(true)
underTest.onTextFieldFocusChanged(isFocused = false)
- setThrottling(false)
+ setLockout(false)
assertThat(isTextFieldFocusRequested).isTrue()
}
@@ -327,24 +327,24 @@
switchToScene(SceneKey.Bouncer)
}
- private suspend fun TestScope.setThrottling(
- isThrottling: Boolean,
+ private suspend fun TestScope.setLockout(
+ isLockedOut: Boolean,
failedAttemptCount: Int = 5,
) {
- if (isThrottling) {
+ if (isLockedOut) {
repeat(failedAttemptCount) {
authenticationRepository.reportAuthenticationAttempt(false)
}
val remainingTimeSeconds = 30
- authenticationRepository.setThrottleDuration(remainingTimeSeconds * 1000)
- authenticationRepository.throttling.value =
- AuthenticationThrottlingModel(
+ authenticationRepository.setLockoutDuration(remainingTimeSeconds * 1000)
+ authenticationRepository.lockout.value =
+ AuthenticationLockoutModel(
failedAttemptCount = failedAttemptCount,
remainingSeconds = remainingTimeSeconds,
)
} else {
authenticationRepository.reportAuthenticationAttempt(true)
- authenticationRepository.throttling.value = null
+ authenticationRepository.lockout.value = null
}
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 862c39c..8971423 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -304,13 +304,12 @@
fun onDragEnd_whenPatternTooShort() =
testScope.runTest {
val message by collectLastValue(bouncerViewModel.message)
- val throttlingDialogMessage by
- collectLastValue(bouncerViewModel.throttlingDialogMessage)
+ val dialogMessage by collectLastValue(bouncerViewModel.dialogMessage)
lockDeviceAndOpenPatternBouncer()
// Enter a pattern that's too short more than enough times that would normally trigger
- // throttling if the pattern were not too short and wrong:
- val attempts = FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING + 1
+ // lockout if the pattern were not too short and wrong:
+ val attempts = FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT + 1
repeat(attempts) { attempt ->
underTest.onDragStart()
CORRECT_PATTERN.subList(
@@ -328,7 +327,7 @@
underTest.onDragEnd()
assertWithMessage("Attempt #$attempt").that(message?.text).isEqualTo(WRONG_PATTERN)
- assertWithMessage("Attempt #$attempt").that(throttlingDialogMessage).isNull()
+ assertWithMessage("Attempt #$attempt").that(dialogMessage).isNull()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index bc4bae0..34f703b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -49,8 +49,10 @@
import com.android.systemui.res.R
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -93,6 +95,8 @@
private lateinit var dockManager: DockManagerFake
private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+ private val kosmos = testKosmos()
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -179,6 +183,7 @@
underTest =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = withDeps.keyguardInteractor,
+ shadeInteractor = kosmos.shadeInteractor,
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
@@ -339,6 +344,31 @@
}
@Test
+ fun quickAffordanceAlwaysVisible_notVisible_restrictedByPolicyManager() =
+ testScope.runTest {
+ whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId))
+ .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL)
+
+ repository.setKeyguardShowing(false)
+ repository.setIsDozing(true)
+ homeControls.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon = ICON,
+ activationState = ActivationState.Active,
+ )
+ )
+
+ val collectedValue by
+ collectLastValue(
+ underTest.quickAffordanceAlwaysVisible(
+ KeyguardQuickAffordancePosition.BOTTOM_START
+ )
+ )
+
+ assertThat(collectedValue).isInstanceOf(KeyguardQuickAffordanceModel.Hidden::class.java)
+ }
+
+ @Test
fun quickAffordanceAlwaysVisible_evenWhenLockScreenNotShowingAndDozing() =
testScope.runTest {
repository.setKeyguardShowing(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt
new file mode 100644
index 0000000..4b96251
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.saver.domain
+
+import android.content.SharedPreferences
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.utils.leaks.FakeDataSaverController
+import kotlin.coroutines.EmptyCoroutineContext
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+
+/** Test [DataSaverDialogDelegate]. */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DataSaverDialogDelegateTest : SysuiTestCase() {
+
+ private val dataSaverController = FakeDataSaverController(LeakCheck())
+
+ private lateinit var sysuiDialogFactory: SystemUIDialog.Factory
+ private lateinit var sysuiDialog: SystemUIDialog
+ private lateinit var dataSaverDialogDelegate: DataSaverDialogDelegate
+
+ @Before
+ fun setup() {
+ sysuiDialog = mock<SystemUIDialog>()
+ sysuiDialogFactory = mock<SystemUIDialog.Factory>()
+
+ dataSaverDialogDelegate =
+ DataSaverDialogDelegate(
+ sysuiDialogFactory,
+ context,
+ EmptyCoroutineContext,
+ dataSaverController,
+ mock<SharedPreferences>()
+ )
+
+ whenever(sysuiDialogFactory.create(eq(dataSaverDialogDelegate), eq(context)))
+ .thenReturn(sysuiDialog)
+ }
+ @Test
+ fun delegateSetsDialogTitleCorrectly() {
+ val expectedResId = R.string.data_saver_enable_title
+
+ dataSaverDialogDelegate.onCreate(sysuiDialog, null)
+
+ verify(sysuiDialog).setTitle(eq(expectedResId))
+ }
+
+ @Test
+ fun delegateSetsDialogMessageCorrectly() {
+ val expectedResId = R.string.data_saver_description
+
+ dataSaverDialogDelegate.onCreate(sysuiDialog, null)
+
+ verify(sysuiDialog).setMessage(expectedResId)
+ }
+
+ @Test
+ fun delegateSetsDialogPositiveButtonCorrectly() {
+ val expectedResId = R.string.data_saver_enable_button
+
+ dataSaverDialogDelegate.onCreate(sysuiDialog, null)
+
+ verify(sysuiDialog).setPositiveButton(eq(expectedResId), any())
+ }
+
+ @Test
+ fun delegateSetsDialogCancelButtonCorrectly() {
+ val expectedResId = R.string.cancel
+
+ dataSaverDialogDelegate.onCreate(sysuiDialog, null)
+
+ verify(sysuiDialog).setNeutralButton(eq(expectedResId), eq(null))
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
new file mode 100644
index 0000000..d182412
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.saver.domain
+
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
+import com.android.systemui.qs.tiles.impl.saver.qsDataSaverTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DataSaverTileMapperTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val dataSaverTileConfig = kosmos.qsDataSaverTileConfig
+
+ // Using lazy (versus =) to make sure we override the right context -- see b/311612168
+ private val mapper by lazy { DataSaverTileMapper(context.orCreateTestableResources.resources) }
+
+ @Test
+ fun activeStateMatchesEnabledModel() {
+ val inputModel = DataSaverTileModel(true)
+
+ val outputState = mapper.map(dataSaverTileConfig, inputModel)
+
+ val expectedState =
+ createDataSaverTileState(
+ QSTileState.ActivationState.ACTIVE,
+ R.drawable.qs_data_saver_icon_on
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun inactiveStateMatchesDisabledModel() {
+ val inputModel = DataSaverTileModel(false)
+
+ val outputState = mapper.map(dataSaverTileConfig, inputModel)
+
+ val expectedState =
+ createDataSaverTileState(
+ QSTileState.ActivationState.INACTIVE,
+ R.drawable.qs_data_saver_icon_off
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ private fun createDataSaverTileState(
+ activationState: QSTileState.ActivationState,
+ iconRes: Int
+ ): QSTileState {
+ val label = context.getString(R.string.data_saver)
+ val secondaryLabel =
+ if (activationState == QSTileState.ActivationState.ACTIVE)
+ context.resources.getStringArray(R.array.tile_states_saver)[2]
+ else if (activationState == QSTileState.ActivationState.INACTIVE)
+ context.resources.getStringArray(R.array.tile_states_saver)[1]
+ else context.resources.getStringArray(R.array.tile_states_saver)[0]
+
+ return QSTileState(
+ { Icon.Resource(iconRes, null) },
+ label,
+ activationState,
+ secondaryLabel,
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+ label,
+ null,
+ QSTileState.SideViewIcon.None,
+ QSTileState.EnabledState.ENABLED,
+ Switch::class.qualifiedName
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractorTest.kt
new file mode 100644
index 0000000..819bd03
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractorTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.saver.domain.interactor
+
+import android.os.UserHandle
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
+import com.android.systemui.utils.leaks.FakeDataSaverController
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DataSaverTileDataInteractorTest : SysuiTestCase() {
+ private val controller: FakeDataSaverController = FakeDataSaverController(LeakCheck())
+ private val underTest: DataSaverTileDataInteractor = DataSaverTileDataInteractor(controller)
+
+ @Test
+ fun isAvailableRegardlessOfController() = runTest {
+ controller.setDataSaverEnabled(false)
+
+ runCurrent()
+ val availability by collectLastValue(underTest.availability(TEST_USER))
+
+ Truth.assertThat(availability).isTrue()
+ }
+
+ @Test
+ fun dataMatchesController() = runTest {
+ controller.setDataSaverEnabled(false)
+ val flowValues: List<DataSaverTileModel> by
+ collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
+
+ runCurrent()
+ controller.setDataSaverEnabled(true)
+ runCurrent()
+ controller.setDataSaverEnabled(false)
+ runCurrent()
+
+ Truth.assertThat(flowValues.size).isEqualTo(3)
+ Truth.assertThat(flowValues.map { it.isEnabled })
+ .containsExactly(false, true, false)
+ .inOrder()
+ }
+
+ private companion object {
+ val TEST_USER = UserHandle.of(1)!!
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..7091cb3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractorTest.kt
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.saver.domain.interactor
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.provider.Settings
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.intentInputs
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.utils.leaks.FakeDataSaverController
+import com.google.common.truth.Truth.assertThat
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DataSaverTileUserActionInteractorTest : SysuiTestCase() {
+ private val qsTileIntentUserActionHandler = FakeQSTileIntentUserInputHandler()
+ private val dataSaverController = FakeDataSaverController(LeakCheck())
+
+ private lateinit var userFileManager: UserFileManager
+ private lateinit var sharedPreferences: SharedPreferences
+ private lateinit var dialogFactory: SystemUIDialog.Factory
+ private lateinit var underTest: DataSaverTileUserActionInteractor
+
+ @Before
+ fun setup() {
+ userFileManager = mock<UserFileManager>()
+ sharedPreferences = mock<SharedPreferences>()
+ dialogFactory = mock<SystemUIDialog.Factory>()
+ whenever(
+ userFileManager.getSharedPreferences(
+ eq(DataSaverTileUserActionInteractor.PREFS),
+ eq(Context.MODE_PRIVATE),
+ eq(context.userId)
+ )
+ )
+ .thenReturn(sharedPreferences)
+
+ underTest =
+ DataSaverTileUserActionInteractor(
+ context,
+ EmptyCoroutineContext,
+ EmptyCoroutineContext,
+ dataSaverController,
+ qsTileIntentUserActionHandler,
+ mock<DialogLaunchAnimator>(),
+ dialogFactory,
+ userFileManager,
+ )
+ }
+
+ /** Since the dialog was shown before, we expect the click to enable the controller. */
+ @Test
+ fun handleClickToEnableDialogShownBefore() = runTest {
+ whenever(
+ sharedPreferences.getBoolean(
+ eq(DataSaverTileUserActionInteractor.DIALOG_SHOWN),
+ any()
+ )
+ )
+ .thenReturn(true)
+ val stateBeforeClick = false
+
+ underTest.handleInput(QSTileInputTestKtx.click(DataSaverTileModel(stateBeforeClick)))
+
+ assertThat(dataSaverController.isDataSaverEnabled).isEqualTo(!stateBeforeClick)
+ }
+
+ /**
+ * The first time the tile is clicked to turn on we expect (1) the enabled state to not change
+ * and (2) the dialog to be shown instead.
+ */
+ @Test
+ fun handleClickToEnableDialogNotShownBefore() = runTest {
+ whenever(
+ sharedPreferences.getBoolean(
+ eq(DataSaverTileUserActionInteractor.DIALOG_SHOWN),
+ any()
+ )
+ )
+ .thenReturn(false)
+ val mockDialog = mock<SystemUIDialog>()
+ whenever(dialogFactory.create(any(), any())).thenReturn(mockDialog)
+ val stateBeforeClick = false
+
+ val input = QSTileInputTestKtx.click(DataSaverTileModel(stateBeforeClick))
+ underTest.handleInput(input)
+
+ assertThat(dataSaverController.isDataSaverEnabled).isEqualTo(stateBeforeClick)
+ verify(mockDialog).show()
+ }
+
+ /** Disabling should flip the state, even if the dialog was not shown before. */
+ @Test
+ fun handleClickToDisableDialogNotShownBefore() = runTest {
+ whenever(
+ sharedPreferences.getBoolean(
+ eq(DataSaverTileUserActionInteractor.DIALOG_SHOWN),
+ any()
+ )
+ )
+ .thenReturn(false)
+ val enabledBeforeClick = true
+
+ underTest.handleInput(QSTileInputTestKtx.click(DataSaverTileModel(enabledBeforeClick)))
+
+ assertThat(dataSaverController.isDataSaverEnabled).isEqualTo(!enabledBeforeClick)
+ }
+
+ @Test
+ fun handleClickToDisableDialogShownBefore() = runTest {
+ whenever(
+ sharedPreferences.getBoolean(
+ eq(DataSaverTileUserActionInteractor.DIALOG_SHOWN),
+ any()
+ )
+ )
+ .thenReturn(true)
+ val enabledBeforeClick = true
+
+ underTest.handleInput(QSTileInputTestKtx.click(DataSaverTileModel(enabledBeforeClick)))
+
+ assertThat(dataSaverController.isDataSaverEnabled).isEqualTo(!enabledBeforeClick)
+ }
+
+ @Test
+ fun handleLongClickWhenEnabled() = runTest {
+ val enabledState = true
+
+ underTest.handleInput(QSTileInputTestKtx.longClick(DataSaverTileModel(enabledState)))
+
+ assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1)
+ val intentInput = qsTileIntentUserActionHandler.intentInputs.last()
+ val actualIntentAction = intentInput.intent.action
+ val expectedIntentAction = Settings.ACTION_DATA_SAVER_SETTINGS
+ assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
+ }
+
+ @Test
+ fun handleLongClickWhenDisabled() = runTest {
+ val enabledState = false
+
+ underTest.handleInput(QSTileInputTestKtx.longClick(DataSaverTileModel(enabledState)))
+
+ assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1)
+ val intentInput = qsTileIntentUserActionHandler.intentInputs.last()
+ val actualIntentAction = intentInput.intent.action
+ val expectedIntentAction = Settings.ACTION_DATA_SAVER_SETTINGS
+ assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index 28f3e93..4cdb08a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -67,9 +67,24 @@
val bounds by collectLastValue(appearanceViewModel.stackBounds)
val top = 200f
+ val left = 0f
val bottom = 550f
- placeholderViewModel.onBoundsChanged(top, bottom)
- assertThat(bounds).isEqualTo(NotificationContainerBounds(top = top, bottom = bottom))
+ val right = 100f
+ placeholderViewModel.onBoundsChanged(
+ left = left,
+ top = top,
+ right = right,
+ bottom = bottom
+ )
+ assertThat(bounds)
+ .isEqualTo(
+ NotificationContainerBounds(
+ left = left,
+ top = top,
+ right = right,
+ bottom = bottom
+ )
+ )
}
@Test
diff --git a/packages/SystemUI/res/drawable/qs_record_issue_icon_off.xml b/packages/SystemUI/res/drawable/qs_record_issue_icon_off.xml
new file mode 100644
index 0000000..bd60431
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_record_issue_icon_off.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M5.76 12.89c0 3.28 2.54 5.97 5.76 6.22v-8.26h-5.4c-.23.64-.36 1.33-.36 2.04zm9.27-5.45l1.01-1.59c.19-.29.1-.67-.19-.86-.29-.19-.68-.1-.86.19l-1.12 1.76c-.59-.19-1.22-.29-1.87-.29s-1.28.1-1.87.29L9.01 5.18c-.18-.29-.57-.38-.86-.19-.29.18-.38.57-.19.86l1.01 1.59c-1.02.57-1.86 1.43-2.43 2.45h10.92c-.57-1.02-1.41-1.88-2.43-2.45zm2.85 3.41h-5.4v8.26c3.22-.25 5.76-2.93 5.76-6.22 0-.71-.13-1.4-.36-2.04z"
+ android:fillColor="#000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/qs_record_issue_icon_on.xml b/packages/SystemUI/res/drawable/qs_record_issue_icon_on.xml
new file mode 100644
index 0000000..fadaf78
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_record_issue_icon_on.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M5.76 12.89c0 3.28 2.54 5.97 5.76 6.22v-8.26h-5.4c-.23.64-.36 1.33-.36 2.04zm9.27-5.45l1.01-1.59c.19-.29.1-.67-.19-.86-.29-.19-.68-.1-.86.19l-1.12 1.76c-.59-.19-1.22-.29-1.87-.29s-1.28.1-1.87.29L9.01 5.18c-.18-.29-.57-.38-.86-.19-.29.18-.38.57-.19.86l1.01 1.59c-1.02.57-1.86 1.43-2.43 2.45h10.92c-.57-1.02-1.41-1.88-2.43-2.45zm2.85 3.41h-5.4v8.26c3.22-.25 5.76-2.93 5.76-6.22 0-.71-.13-1.4-.36-2.04z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 66c57fc..6d7ce06 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -106,5 +106,5 @@
</FrameLayout>
<include layout="@layout/ambient_indication"
- android:id="@+id/ambient_indication_container" />
+ android:id="@id/ambient_indication_container" />
</com.android.systemui.statusbar.phone.KeyguardBottomAreaView>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 1838795..cf63cc7 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -223,6 +223,8 @@
<item type="id" name="lock_icon_bg" />
<item type="id" name="burn_in_layer" />
<item type="id" name="communal_tutorial_indicator" />
+ <item type="id" name="nssl_placeholder_barrier_bottom" />
+ <item type="id" name="ambient_indication_container" />
<!-- Privacy dialog -->
<item type="id" name="privacy_dialog_close_app_button" />
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7ca0b6e..78b701c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -824,6 +824,13 @@
<!-- QuickSettings: Text to prompt the user to stop an ongoing recording [CHAR LIMIT=20] -->
<string name="quick_settings_screen_record_stop">Stop</string>
+ <!-- QuickSettings: Record Issue tile [CHAR LIMIT=NONE] -->
+ <string name="qs_record_issue_label">Record Issue</string>
+ <!-- QuickSettings: Text to prompt the user to begin a new recording [CHAR LIMIT=20] -->
+ <string name="qs_record_issue_start">Start</string>
+ <!-- QuickSettings: Text to prompt the user to stop an ongoing recording [CHAR LIMIT=20] -->
+ <string name="qs_record_issue_stop">Stop</string>
+
<!-- QuickSettings: Label for the toggle that controls whether One-handed mode is enabled. [CHAR LIMIT=NONE] -->
<string name="quick_settings_onehanded_label">One-handed mode</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
index c505bd5..df7182b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
@@ -21,6 +21,7 @@
import android.text.TextUtils;
import android.view.View;
+import com.android.internal.jank.Cuj;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.jank.InteractionJankMonitor.Configuration;
@@ -29,40 +30,25 @@
public final class InteractionJankMonitorWrapper {
// Launcher journeys.
- public static final int CUJ_APP_LAUNCH_FROM_RECENTS =
- InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS;
- public static final int CUJ_APP_LAUNCH_FROM_ICON =
- InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON;
- public static final int CUJ_APP_CLOSE_TO_HOME =
- InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_HOME;
+ public static final int CUJ_APP_LAUNCH_FROM_RECENTS = Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS;
+ public static final int CUJ_APP_LAUNCH_FROM_ICON = Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON;
+ public static final int CUJ_APP_CLOSE_TO_HOME = Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME;
public static final int CUJ_APP_CLOSE_TO_HOME_FALLBACK =
- InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
- public static final int CUJ_APP_CLOSE_TO_PIP =
- InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP;
- public static final int CUJ_QUICK_SWITCH =
- InteractionJankMonitor.CUJ_LAUNCHER_QUICK_SWITCH;
- public static final int CUJ_OPEN_ALL_APPS =
- InteractionJankMonitor.CUJ_LAUNCHER_OPEN_ALL_APPS;
- public static final int CUJ_CLOSE_ALL_APPS_SWIPE =
- InteractionJankMonitor.CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE;
- public static final int CUJ_CLOSE_ALL_APPS_TO_HOME =
- InteractionJankMonitor.CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME;
- public static final int CUJ_ALL_APPS_SCROLL =
- InteractionJankMonitor.CUJ_LAUNCHER_ALL_APPS_SCROLL;
- public static final int CUJ_APP_LAUNCH_FROM_WIDGET =
- InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET;
- public static final int CUJ_SPLIT_SCREEN_ENTER =
- InteractionJankMonitor.CUJ_SPLIT_SCREEN_ENTER;
+ Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
+ public static final int CUJ_APP_CLOSE_TO_PIP = Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_PIP;
+ public static final int CUJ_QUICK_SWITCH = Cuj.CUJ_LAUNCHER_QUICK_SWITCH;
+ public static final int CUJ_OPEN_ALL_APPS = Cuj.CUJ_LAUNCHER_OPEN_ALL_APPS;
+ public static final int CUJ_CLOSE_ALL_APPS_SWIPE = Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE;
+ public static final int CUJ_CLOSE_ALL_APPS_TO_HOME = Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME;
+ public static final int CUJ_ALL_APPS_SCROLL = Cuj.CUJ_LAUNCHER_ALL_APPS_SCROLL;
+ public static final int CUJ_APP_LAUNCH_FROM_WIDGET = Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET;
+ public static final int CUJ_SPLIT_SCREEN_ENTER = Cuj.CUJ_SPLIT_SCREEN_ENTER;
public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION =
- InteractionJankMonitor.CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
- public static final int CUJ_RECENTS_SCROLLING =
- InteractionJankMonitor.CUJ_RECENTS_SCROLLING;
- public static final int CUJ_APP_SWIPE_TO_RECENTS =
- InteractionJankMonitor.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS;
- public static final int CUJ_OPEN_SEARCH_RESULT =
- InteractionJankMonitor.CUJ_LAUNCHER_OPEN_SEARCH_RESULT;
- public static final int CUJ_LAUNCHER_UNFOLD_ANIM =
- InteractionJankMonitor.CUJ_LAUNCHER_UNFOLD_ANIM;
+ Cuj.CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
+ public static final int CUJ_RECENTS_SCROLLING = Cuj.CUJ_RECENTS_SCROLLING;
+ public static final int CUJ_APP_SWIPE_TO_RECENTS = Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS;
+ public static final int CUJ_OPEN_SEARCH_RESULT = Cuj.CUJ_LAUNCHER_OPEN_SEARCH_RESULT;
+ public static final int CUJ_LAUNCHER_UNFOLD_ANIM = Cuj.CUJ_LAUNCHER_UNFOLD_ANIM;
@IntDef({
CUJ_APP_LAUNCH_FROM_RECENTS,
@@ -89,7 +75,7 @@
* Begin a trace session.
*
* @param v an attached view.
- * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+ * @param cujType the specific {@link Cuj.CujType}.
*/
public static void begin(View v, @CujType int cujType) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return;
@@ -100,7 +86,7 @@
* Begin a trace session.
*
* @param v an attached view.
- * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+ * @param cujType the specific {@link Cuj.CujType}.
* @param timeout duration to cancel the instrumentation in ms
*/
public static void begin(View v, @CujType int cujType, long timeout) {
@@ -115,7 +101,7 @@
* Begin a trace session.
*
* @param v an attached view.
- * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+ * @param cujType the specific {@link Cuj.CujType}.
* @param tag the tag to distinguish different flow of same type CUJ.
*/
public static void begin(View v, @CujType int cujType, String tag) {
@@ -131,7 +117,7 @@
/**
* End a trace session.
*
- * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+ * @param cujType the specific {@link Cuj.CujType}.
*/
public static void end(@CujType int cujType) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java
index 38a8cd3..c4aa7a2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java
@@ -104,4 +104,14 @@
*/
default void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) {
}
+
+ /**
+ * Shows the security screen that should be shown.
+ *
+ * This can be considered as a "refresh" of the bouncer view. Based on certain parameters,
+ * we might switch to a different bouncer screen. e.g. SimPin to SimPuk.
+ */
+ default void showCurrentSecurityScreen() {
+
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index f706301..0a4378e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -27,6 +27,8 @@
import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_PRIMARY;
import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_SECONDARY_USER;
import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_WORK_PROFILE;
+import static com.android.keyguard.KeyguardSecurityModel.SecurityMode.SimPin;
+import static com.android.keyguard.KeyguardSecurityModel.SecurityMode.SimPuk;
import static com.android.systemui.DejankUtils.whitelistIpcs;
import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
import static com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES;
@@ -99,6 +101,7 @@
import dagger.Lazy;
import java.io.File;
+import java.util.Arrays;
import java.util.Optional;
import javax.inject.Inject;
@@ -164,8 +167,8 @@
}
mCurrentUser = mSelectedUserInteractor.getSelectedUserId();
showPrimarySecurityScreen(false);
- if (mCurrentSecurityMode != SecurityMode.SimPin
- && mCurrentSecurityMode != SecurityMode.SimPuk) {
+ if (mCurrentSecurityMode != SimPin
+ && mCurrentSecurityMode != SimPuk) {
reinflateViewFlipper((l) -> {
});
}
@@ -334,6 +337,11 @@
public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) {
mViewMediatorCallback.setNeedsInput(needsInput);
}
+
+ @Override
+ public void showCurrentSecurityScreen() {
+ showPrimarySecurityScreen(false);
+ }
};
private final SwipeListener mSwipeListener = new SwipeListener() {
@@ -888,7 +896,8 @@
finish = true;
eventSubtype = BOUNCER_DISMISS_SIM;
uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM;
- } else {
+ } else if (Arrays.asList(SimPin, SimPuk).contains(securityMode)) {
+ // There are additional screens to the sim pin/puk flow.
showSecurityScreen(securityMode);
}
break;
@@ -1095,8 +1104,8 @@
}
private void configureMode() {
- boolean useSimSecurity = mCurrentSecurityMode == SecurityMode.SimPin
- || mCurrentSecurityMode == SecurityMode.SimPuk;
+ boolean useSimSecurity = mCurrentSecurityMode == SimPin
+ || mCurrentSecurityMode == SimPuk;
int mode = KeyguardSecurityContainer.MODE_DEFAULT;
if (canDisplayUserSwitcher() && !useSimSecurity) {
mode = KeyguardSecurityContainer.MODE_USER_SWITCHER;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 6e24208..c5e7070 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -16,6 +16,8 @@
package com.android.keyguard;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
import android.annotation.NonNull;
@@ -60,7 +62,7 @@
// When this is true and when SIM card is PIN locked state, on PIN lock screen, message would
// be displayed to inform user about the number of remaining PIN attempts left.
private boolean mShowDefaultMessage;
- private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ private int mSubId = INVALID_SUBSCRIPTION_ID;
private AlertDialog mRemainingAttemptsDialog;
private ImageView mSimImageView;
@@ -68,6 +70,12 @@
@Override
public void onSimStateChanged(int subId, int slotId, int simState) {
if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
+ // If subId has gone to PUK required then we need to go to the PUK screen.
+ if (subId == mSubId && simState == TelephonyManager.SIM_STATE_PUK_REQUIRED) {
+ getKeyguardSecurityCallback().showCurrentSecurityScreen();
+ return;
+ }
+
if (simState == TelephonyManager.SIM_STATE_READY) {
mRemainingAttempts = -1;
resetState();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 37bd9b2..9c61a8a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1277,6 +1277,17 @@
private final FaceAuthenticationListener mFaceAuthenticationListener =
new FaceAuthenticationListener() {
+ public void onAuthenticatedChanged(boolean isAuthenticated) {
+ if (!isAuthenticated) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onFacesCleared();
+ }
+ }
+ }
+ }
+
@Override
public void onAuthEnrollmentStateChanged(boolean enrolled) {
notifyAboutEnrollmentChange(TYPE_FACE);
@@ -1961,7 +1972,7 @@
protected void handleStartedGoingToSleep(int arg1) {
Assert.isMainThread();
- clearBiometricRecognized();
+ clearFingerprintRecognized();
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -3010,7 +3021,7 @@
void handleUserSwitching(int userId, Runnable resultCallback) {
mLogger.logUserSwitching(userId, "from UserTracker");
Assert.isMainThread();
- clearBiometricRecognized();
+ clearFingerprintRecognized();
boolean trustUsuallyManaged = mTrustManager.isTrustUsuallyManaged(userId);
mLogger.logTrustUsuallyManagedUpdated(userId, mUserTrustIsUsuallyManaged.get(userId),
trustUsuallyManaged, "userSwitching");
@@ -3560,25 +3571,30 @@
return mServiceStates.get(subId);
}
- public void clearBiometricRecognized() {
- clearBiometricRecognized(UserHandle.USER_NULL);
+ /**
+ * Resets the fingerprint authenticated state to false.
+ */
+ public void clearFingerprintRecognized() {
+ clearFingerprintRecognized(UserHandle.USER_NULL);
}
- public void clearBiometricRecognizedWhenKeyguardDone(int unlockedUser) {
- clearBiometricRecognized(unlockedUser);
+ /**
+ * Resets the fingerprint authenticated state to false.
+ */
+ public void clearFingerprintRecognizedWhenKeyguardDone(int unlockedUser) {
+ clearFingerprintRecognized(unlockedUser);
}
- private void clearBiometricRecognized(int unlockedUser) {
+ private void clearFingerprintRecognized(int unlockedUser) {
Assert.isMainThread();
mUserFingerprintAuthenticated.clear();
mTrustManager.clearAllBiometricRecognized(FINGERPRINT, unlockedUser);
- mTrustManager.clearAllBiometricRecognized(FACE, unlockedUser);
- mLogger.d("clearBiometricRecognized");
+ mLogger.d("clearFingerprintRecognized");
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onBiometricsCleared();
+ cb.onFingerprintsCleared();
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 02dd331..9d216dce 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -291,9 +291,14 @@
public void onLogoutEnabledChanged() { }
/**
- * Called when authenticated biometrics are cleared.
+ * Called when authenticated fingerprint biometrics are cleared.
*/
- public void onBiometricsCleared() { }
+ public void onFingerprintsCleared() { }
+
+ /**
+ * Called when authenticated face biometrics have cleared.
+ */
+ public void onFacesCleared() { }
/**
* Called when the secondary lock screen requirement changes.
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index bd84b28..fda23b7f 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -24,9 +24,9 @@
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockscreenCredential
import com.android.keyguard.KeyguardSecurityModel
+import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationResultModel
-import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -80,13 +80,14 @@
val isPatternVisible: StateFlow<Boolean>
/**
- * The current authentication throttling state, set when the user has to wait before being able
- * to try another authentication attempt. `null` indicates throttling isn't active.
+ * The current authentication lockout (aka "throttling") state, set when the user has to wait
+ * before being able to try another authentication attempt. `null` indicates throttling isn't
+ * active.
*/
- val throttling: MutableStateFlow<AuthenticationThrottlingModel?>
+ val lockout: MutableStateFlow<AuthenticationLockoutModel?>
/** Whether throttling has occurred at least once since the last successful authentication. */
- val hasThrottlingOccurred: MutableStateFlow<Boolean>
+ val hasLockoutOccurred: MutableStateFlow<Boolean>
/**
* Whether the auto confirm feature is enabled for the currently-selected user.
@@ -138,22 +139,25 @@
/** Reports an authentication attempt. */
suspend fun reportAuthenticationAttempt(isSuccessful: Boolean)
+ /** Reports that the user has entered a temporary device lockout (throttling). */
+ suspend fun reportLockoutStarted(durationMs: Int)
+
/** Returns the current number of failed authentication attempts. */
suspend fun getFailedAuthenticationAttemptCount(): Int
/**
- * Returns the timestamp for when the current throttling will end, allowing the user to attempt
+ * Returns the timestamp for when the current lockout will end, allowing the user to attempt
* authentication again.
*
* Note that this is in milliseconds and it matches [SystemClock.elapsedRealtime].
*/
- suspend fun getThrottlingEndTimestamp(): Long
+ suspend fun getLockoutEndTimestamp(): Long
/**
- * Sets the throttling timeout duration (time during which the user should not be allowed to
+ * Sets the lockout timeout duration (time during which the user should not be allowed to
* attempt authentication).
*/
- suspend fun setThrottleDuration(durationMs: Int)
+ suspend fun setLockoutDuration(durationMs: Int)
/**
* Checks the given [LockscreenCredential] to see if it's correct, returning an
@@ -185,10 +189,9 @@
getFreshValue = lockPatternUtils::isVisiblePatternEnabled,
)
- override val throttling: MutableStateFlow<AuthenticationThrottlingModel?> =
- MutableStateFlow(null)
+ override val lockout: MutableStateFlow<AuthenticationLockoutModel?> = MutableStateFlow(null)
- override val hasThrottlingOccurred: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ override val hasLockoutOccurred: MutableStateFlow<Boolean> = MutableStateFlow(false)
override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> =
refreshingFlow(
@@ -252,19 +255,25 @@
}
}
+ override suspend fun reportLockoutStarted(durationMs: Int) {
+ return withContext(backgroundDispatcher) {
+ lockPatternUtils.reportPasswordLockout(durationMs, selectedUserId)
+ }
+ }
+
override suspend fun getFailedAuthenticationAttemptCount(): Int {
return withContext(backgroundDispatcher) {
lockPatternUtils.getCurrentFailedPasswordAttempts(selectedUserId)
}
}
- override suspend fun getThrottlingEndTimestamp(): Long {
+ override suspend fun getLockoutEndTimestamp(): Long {
return withContext(backgroundDispatcher) {
lockPatternUtils.getLockoutAttemptDeadline(selectedUserId)
}
}
- override suspend fun setThrottleDuration(durationMs: Int) {
+ override suspend fun setLockoutDuration(durationMs: Int) {
withContext(backgroundDispatcher) {
lockPatternUtils.setLockoutAttemptDeadline(selectedUserId, durationMs)
}
@@ -276,9 +285,9 @@
return withContext(backgroundDispatcher) {
try {
val matched = lockPatternUtils.checkCredential(credential, selectedUserId) {}
- AuthenticationResultModel(isSuccessful = matched, throttleDurationMs = 0)
+ AuthenticationResultModel(isSuccessful = matched, lockoutDurationMs = 0)
} catch (ex: LockPatternUtils.RequestThrottledException) {
- AuthenticationResultModel(isSuccessful = false, throttleDurationMs = ex.timeoutMs)
+ AuthenticationResultModel(isSuccessful = false, lockoutDurationMs = ex.timeoutMs)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 7f8f887..797154e 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -20,9 +20,9 @@
import com.android.internal.widget.LockPatternView
import com.android.internal.widget.LockscreenCredential
import com.android.systemui.authentication.data.repository.AuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
-import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -85,10 +85,11 @@
val authenticationMethod: Flow<AuthenticationMethodModel> = repository.authenticationMethod
/**
- * The current authentication throttling state, set when the user has to wait before being able
- * to try another authentication attempt. `null` indicates throttling isn't active.
+ * The current authentication lockout (aka "throttling") state, set when the user has to wait
+ * before being able to try another authentication attempt. `null` indicates lockout isn't
+ * active.
*/
- val throttling: StateFlow<AuthenticationThrottlingModel?> = repository.throttling
+ val lockout: StateFlow<AuthenticationLockoutModel?> = repository.lockout
/**
* Whether the auto confirm feature is enabled for the currently-selected user.
@@ -97,12 +98,12 @@
* [hintedPinLength].
*/
val isAutoConfirmEnabled: StateFlow<Boolean> =
- combine(repository.isAutoConfirmFeatureEnabled, repository.hasThrottlingOccurred) {
+ combine(repository.isAutoConfirmFeatureEnabled, repository.hasLockoutOccurred) {
featureEnabled,
- hasThrottlingOccurred ->
- // Disable auto-confirm if throttling occurred since the last successful
+ hasLockoutOccurred ->
+ // Disable auto-confirm if lockout occurred since the last successful
// authentication attempt.
- featureEnabled && !hasThrottlingOccurred
+ featureEnabled && !hasLockoutOccurred
}
.stateIn(
scope = applicationScope,
@@ -139,7 +140,7 @@
/** Whether the "enhanced PIN privacy" setting is enabled for the current user. */
val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = repository.isPinEnhancedPrivacyEnabled
- private var throttlingCountdownJob: Job? = null
+ private var lockoutCountdownJob: Job? = null
init {
applicationScope.launch {
@@ -188,8 +189,8 @@
val authMethod = getAuthenticationMethod()
val skipCheck =
when {
- // Throttling is active, the UI layer should not have called this; skip the attempt.
- throttling.value != null -> true
+ // Lockout is active, the UI layer should not have called this; skip the attempt.
+ lockout.value != null -> true
// The input is too short; skip the attempt.
input.isTooShort(authMethod) -> true
// Auto-confirm attempt when the feature is not enabled; skip the attempt.
@@ -215,21 +216,22 @@
)
}
- // Check if we need to throttle and, if so, kick off the throttle countdown:
- if (!authenticationResult.isSuccessful && authenticationResult.throttleDurationMs > 0) {
- repository.setThrottleDuration(
- durationMs = authenticationResult.throttleDurationMs,
- )
- repository.hasThrottlingOccurred.value = true
- startThrottlingCountdown()
+ // Check if lockout should start and, if so, kick off the countdown:
+ if (!authenticationResult.isSuccessful && authenticationResult.lockoutDurationMs > 0) {
+ repository.apply {
+ setLockoutDuration(durationMs = authenticationResult.lockoutDurationMs)
+ reportLockoutStarted(durationMs = authenticationResult.lockoutDurationMs)
+ hasLockoutOccurred.value = true
+ }
+ startLockoutCountdown()
}
if (authenticationResult.isSuccessful) {
- // Since authentication succeeded, we should refresh throttling to make sure that our
- // state is completely reflecting the upstream source of truth.
- refreshThrottling()
+ // Since authentication succeeded, refresh lockout to make sure the state is completely
+ // reflecting the upstream source of truth.
+ refreshLockout()
- repository.hasThrottlingOccurred.value = false
+ repository.hasLockoutOccurred.value = false
}
return if (authenticationResult.isSuccessful) {
@@ -247,52 +249,52 @@
}
}
- /** Starts refreshing the throttling state every second. */
- private suspend fun startThrottlingCountdown() {
- cancelThrottlingCountdown()
- throttlingCountdownJob =
+ /** Starts refreshing the lockout state every second. */
+ private suspend fun startLockoutCountdown() {
+ cancelLockoutCountdown()
+ lockoutCountdownJob =
applicationScope.launch {
- while (refreshThrottling()) {
+ while (refreshLockout()) {
delay(1.seconds.inWholeMilliseconds)
}
}
}
- /** Cancels any throttling state countdown started in [startThrottlingCountdown]. */
- private fun cancelThrottlingCountdown() {
- throttlingCountdownJob?.cancel()
- throttlingCountdownJob = null
+ /** Cancels any lockout state countdown started in [startLockoutCountdown]. */
+ private fun cancelLockoutCountdown() {
+ lockoutCountdownJob?.cancel()
+ lockoutCountdownJob = null
}
/** Notifies that the currently-selected user has changed. */
private suspend fun onSelectedUserChanged() {
- cancelThrottlingCountdown()
- if (refreshThrottling()) {
- startThrottlingCountdown()
+ cancelLockoutCountdown()
+ if (refreshLockout()) {
+ startLockoutCountdown()
}
}
/**
- * Refreshes the throttling state, hydrating the repository with the latest state.
+ * Refreshes the lockout state, hydrating the repository with the latest state.
*
- * @return Whether throttling is active or not.
+ * @return Whether lockout is active or not.
*/
- private suspend fun refreshThrottling(): Boolean {
- withContext("$TAG#refreshThrottling", backgroundDispatcher) {
+ private suspend fun refreshLockout(): Boolean {
+ withContext("$TAG#refreshLockout", backgroundDispatcher) {
val failedAttemptCount = async { repository.getFailedAuthenticationAttemptCount() }
- val deadline = async { repository.getThrottlingEndTimestamp() }
+ val deadline = async { repository.getLockoutEndTimestamp() }
val remainingMs = max(0, deadline.await() - clock.elapsedRealtime())
- repository.throttling.value =
+ repository.lockout.value =
if (remainingMs > 0) {
- AuthenticationThrottlingModel(
+ AuthenticationLockoutModel(
failedAttemptCount = failedAttemptCount.await(),
remainingSeconds = ceil(remainingMs / 1000f).toInt(),
)
} else {
- null // Throttling ended.
+ null // Lockout ended.
}
}
- return repository.throttling.value != null
+ return repository.lockout.value != null
}
private fun AuthenticationMethodModel.createCredential(
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationLockoutModel.kt
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt
rename to packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationLockoutModel.kt
index 8392528..8ee2d5e 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationLockoutModel.kt
@@ -16,17 +16,17 @@
package com.android.systemui.authentication.shared.model
-/** Models a state for throttling the next authentication attempt. */
-data class AuthenticationThrottlingModel(
+/** Models a state for temporarily locking out the next authentication attempt. */
+data class AuthenticationLockoutModel(
- /** Number of failed authentication attempts so far. If not throttling this will be `0`. */
+ /** Number of failed authentication attempts so far. If not locked out this will be `0`. */
val failedAttemptCount: Int = 0,
/**
* Remaining amount of time, in seconds, before another authentication attempt can be done. If
- * not throttling this will be `0`.
+ * not locked out this will be `0`.
*
- * This number is changed throughout the timeout.
+ * This number is changed throughout the lockout.
*
* Note: this isn't precise (in milliseconds), but rounded up to ensure "at most" this amount of
* seconds remains.
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt
index f2a3e74..addc75e 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt
@@ -21,5 +21,5 @@
/** Whether authentication was successful. */
val isSuccessful: Boolean = false,
/** If [isSuccessful] is `false`, how long the user must wait before trying again. */
- val throttleDurationMs: Int = 0,
+ val lockoutDurationMs: Int = 0,
)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index a2ac66f..63fe26a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -197,11 +197,42 @@
listenForGoneToAodTransition(this)
listenForLockscreenAodTransitions(this)
listenForAodToOccludedTransitions(this)
+ listenForAlternateBouncerToAodTransitions(this)
+ listenForDreamingToAodTransitions(this)
}
}
}
@VisibleForTesting
+ suspend fun listenForDreamingToAodTransitions(scope: CoroutineScope): Job {
+ return scope.launch {
+ transitionInteractor.transition(KeyguardState.DREAMING, KeyguardState.AOD).collect {
+ transitionStep ->
+ view.onDozeAmountChanged(
+ transitionStep.value,
+ transitionStep.value,
+ ANIMATE_APPEAR_ON_SCREEN_OFF,
+ )
+ }
+ }
+ }
+
+ @VisibleForTesting
+ suspend fun listenForAlternateBouncerToAodTransitions(scope: CoroutineScope): Job {
+ return scope.launch {
+ transitionInteractor
+ .transition(KeyguardState.ALTERNATE_BOUNCER, KeyguardState.AOD)
+ .collect { transitionStep ->
+ view.onDozeAmountChanged(
+ transitionStep.value,
+ transitionStep.value,
+ UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN,
+ )
+ }
+ }
+ }
+
+ @VisibleForTesting
suspend fun listenForAodToOccludedTransitions(scope: CoroutineScope): Job {
return scope.launch {
transitionInteractor.transition(KeyguardState.AOD, KeyguardState.OCCLUDED).collect {
@@ -246,7 +277,10 @@
suspend fun listenForLockscreenAodTransitions(scope: CoroutineScope): Job {
return scope.launch {
transitionInteractor.dozeAmountTransition.collect { transitionStep ->
- if (transitionStep.transitionState == TransitionState.CANCELED) {
+ if (
+ transitionStep.from == KeyguardState.AOD &&
+ transitionStep.transitionState == TransitionState.CANCELED
+ ) {
if (
transitionInteractor.startedKeyguardTransitionStep.first().to !=
KeyguardState.AOD
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 677f60d..724c0fe 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -19,8 +19,8 @@
import android.content.Context
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.domain.interactor.AuthenticationResult
+import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.bouncer.data.repository.BouncerRepository
import com.android.systemui.classifier.FalsingClassifier
import com.android.systemui.classifier.domain.interactor.FalsingInteractor
@@ -60,24 +60,25 @@
/** The user-facing message to show in the bouncer. */
val message: StateFlow<String?> =
- combine(repository.message, authenticationInteractor.throttling) { message, throttling ->
- messageOrThrottlingMessage(message, throttling)
+ combine(repository.message, authenticationInteractor.lockout) { message, lockout ->
+ messageOrLockoutMessage(message, lockout)
}
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
initialValue =
- messageOrThrottlingMessage(
+ messageOrLockoutMessage(
repository.message.value,
- authenticationInteractor.throttling.value,
+ authenticationInteractor.lockout.value,
)
)
/**
- * The current authentication throttling state, set when the user has to wait before being able
- * to try another authentication attempt. `null` indicates throttling isn't active.
+ * The current authentication lockout (aka "throttling") state, set when the user has to wait
+ * before being able to try another authentication attempt. `null` indicates lockout isn't
+ * active.
*/
- val throttling: StateFlow<AuthenticationThrottlingModel?> = authenticationInteractor.throttling
+ val lockout: StateFlow<AuthenticationLockoutModel?> = authenticationInteractor.lockout
/** Whether the auto confirm feature is enabled for the currently-selected user. */
val isAutoConfirmEnabled: StateFlow<Boolean> = authenticationInteractor.isAutoConfirmEnabled
@@ -102,9 +103,9 @@
init {
if (flags.isEnabled()) {
- // Clear the message if moved from throttling to no-longer throttling.
+ // Clear the message if moved from locked-out to no-longer locked-out.
applicationScope.launch {
- throttling.pairwise().collect { (previous, current) ->
+ lockout.pairwise().collect { (previous, current) ->
if (previous != null && current == null) {
clearMessage()
}
@@ -213,9 +214,9 @@
* Shows the error message.
*
* Callers should use this instead of [authenticate] when they know ahead of time that an auth
- * attempt will fail but aren't interested in the other side effects like triggering throttling.
+ * attempt will fail but aren't interested in the other side effects like triggering lockout.
* For example, if the user entered a pattern that's too short, the system can show the error
- * message without having the attempt trigger throttling.
+ * message without having the attempt trigger lockout.
*/
private suspend fun showErrorMessage() {
repository.setMessage(errorMessage(authenticationInteractor.getAuthenticationMethod()))
@@ -250,15 +251,15 @@
}
}
- private fun messageOrThrottlingMessage(
+ private fun messageOrLockoutMessage(
message: String?,
- throttlingModel: AuthenticationThrottlingModel?,
+ lockoutModel: AuthenticationLockoutModel?,
): String {
return when {
- throttlingModel != null ->
+ lockoutModel != null ->
applicationContext.getString(
com.android.internal.R.string.lockscreen_too_many_failed_attempts_countdown,
- throttlingModel.remainingSeconds,
+ lockoutModel.remainingSeconds,
)
message != null -> message
else -> ""
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt
index 5385442..7f97718 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt
@@ -21,13 +21,13 @@
/** Enumerates all known adaptive layout configurations. */
enum class BouncerSceneLayout {
/** The default UI with the bouncer laid out normally. */
- STANDARD,
+ STANDARD_BOUNCER,
/** The bouncer is displayed vertically stacked with the user switcher. */
- STACKED,
+ BELOW_USER_SWITCHER,
/** The bouncer is displayed side-by-side with the user switcher or an empty space. */
- SIDE_BY_SIDE,
+ BESIDE_USER_SWITCHER,
/** The bouncer is split in two with both sides shown side-by-side. */
- SPLIT,
+ SPLIT_BOUNCER,
}
/** Enumerates the supported window size classes. */
@@ -48,19 +48,19 @@
isSideBySideSupported: Boolean,
): BouncerSceneLayout {
return when (height) {
- SizeClass.COMPACT -> BouncerSceneLayout.SPLIT
+ SizeClass.COMPACT -> BouncerSceneLayout.SPLIT_BOUNCER
SizeClass.MEDIUM ->
when (width) {
- SizeClass.COMPACT -> BouncerSceneLayout.STANDARD
- SizeClass.MEDIUM -> BouncerSceneLayout.STANDARD
- SizeClass.EXPANDED -> BouncerSceneLayout.SIDE_BY_SIDE
+ SizeClass.COMPACT -> BouncerSceneLayout.STANDARD_BOUNCER
+ SizeClass.MEDIUM -> BouncerSceneLayout.STANDARD_BOUNCER
+ SizeClass.EXPANDED -> BouncerSceneLayout.BESIDE_USER_SWITCHER
}
SizeClass.EXPANDED ->
when (width) {
- SizeClass.COMPACT -> BouncerSceneLayout.STANDARD
- SizeClass.MEDIUM -> BouncerSceneLayout.STACKED
- SizeClass.EXPANDED -> BouncerSceneLayout.SIDE_BY_SIDE
+ SizeClass.COMPACT -> BouncerSceneLayout.STANDARD_BOUNCER
+ SizeClass.MEDIUM -> BouncerSceneLayout.BELOW_USER_SWITCHER
+ SizeClass.EXPANDED -> BouncerSceneLayout.BESIDE_USER_SWITCHER
}
- }.takeIf { it != BouncerSceneLayout.SIDE_BY_SIDE || isSideBySideSupported }
- ?: BouncerSceneLayout.STANDARD
+ }.takeIf { it != BouncerSceneLayout.BESIDE_USER_SWITCHER || isSideBySideSupported }
+ ?: BouncerSceneLayout.STANDARD_BOUNCER
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index e379dab..0d7f6dc 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -50,12 +50,12 @@
abstract val authenticationMethod: AuthenticationMethodModel
/**
- * String resource ID of the failure message to be shown during throttling.
+ * String resource ID of the failure message to be shown during lockout.
*
* The message must include 2 number parameters: the first one indicating how many unsuccessful
- * attempts were made, and the second one indicating in how many seconds throttling will expire.
+ * attempts were made, and the second one indicating in how many seconds lockout will expire.
*/
- @get:StringRes abstract val throttlingMessageId: Int
+ @get:StringRes abstract val lockoutMessageId: Int
/** Notifies that the UI has been shown to the user. */
fun onShown() {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index fefc3e3..4b14343 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -105,17 +105,17 @@
get() = bouncerInteractor.isUserSwitcherVisible
private val isInputEnabled: StateFlow<Boolean> =
- bouncerInteractor.throttling
+ bouncerInteractor.lockout
.map { it == null }
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = bouncerInteractor.throttling.value == null,
+ initialValue = bouncerInteractor.lockout.value == null,
)
// Handle to the scope of the child ViewModel (stored in [authMethod]).
private var childViewModelScope: CoroutineScope? = null
- private val _throttlingDialogMessage = MutableStateFlow<String?>(null)
+ private val _dialogMessage = MutableStateFlow<String?>(null)
/** View-model for the current UI, based on the current authentication method. */
val authMethodViewModel: StateFlow<AuthMethodBouncerViewModel?> =
@@ -128,20 +128,20 @@
)
/**
- * A message for a throttling dialog to show when the user has attempted the wrong credential
- * too many times and now must wait a while before attempting again.
+ * A message for a dialog to show when the user has attempted the wrong credential too many
+ * times and now must wait a while before attempting again.
*
* If `null`, no dialog should be shown.
*
- * Once the dialog is shown, the UI should call [onThrottlingDialogDismissed] when the user
- * dismisses this dialog.
+ * Once the dialog is shown, the UI should call [onDialogDismissed] when the user dismisses this
+ * dialog.
*/
- val throttlingDialogMessage: StateFlow<String?> = _throttlingDialogMessage.asStateFlow()
+ val dialogMessage: StateFlow<String?> = _dialogMessage.asStateFlow()
/** The user-facing message to show in the bouncer. */
val message: StateFlow<MessageViewModel> =
- combine(bouncerInteractor.message, bouncerInteractor.throttling) { message, throttling ->
- toMessageViewModel(message, isThrottled = throttling != null)
+ combine(bouncerInteractor.message, bouncerInteractor.lockout) { message, lockout ->
+ toMessageViewModel(message, isLockedOut = lockout != null)
}
.stateIn(
scope = applicationScope,
@@ -149,7 +149,7 @@
initialValue =
toMessageViewModel(
message = bouncerInteractor.message.value,
- isThrottled = bouncerInteractor.throttling.value != null,
+ isLockedOut = bouncerInteractor.lockout.value != null,
),
)
@@ -197,28 +197,28 @@
init {
if (flags.isEnabled()) {
applicationScope.launch {
- combine(bouncerInteractor.throttling, authMethodViewModel) {
- throttling,
+ combine(bouncerInteractor.lockout, authMethodViewModel) {
+ lockout,
authMethodViewModel ->
- if (throttling != null && authMethodViewModel != null) {
+ if (lockout != null && authMethodViewModel != null) {
applicationContext.getString(
- authMethodViewModel.throttlingMessageId,
- throttling.failedAttemptCount,
- throttling.remainingSeconds,
+ authMethodViewModel.lockoutMessageId,
+ lockout.failedAttemptCount,
+ lockout.remainingSeconds,
)
} else {
null
}
}
.distinctUntilChanged()
- .collect { dialogMessage -> _throttlingDialogMessage.value = dialogMessage }
+ .collect { dialogMessage -> _dialogMessage.value = dialogMessage }
}
}
}
- /** Notifies that a throttling dialog has been dismissed by the user. */
- fun onThrottlingDialogDismissed() {
- _throttlingDialogMessage.value = null
+ /** Notifies that the dialog has been dismissed by the user. */
+ fun onDialogDismissed() {
+ _dialogMessage.value = null
}
private fun isSideBySideSupported(authMethod: AuthMethodBouncerViewModel?): Boolean {
@@ -231,11 +231,11 @@
private fun toMessageViewModel(
message: String?,
- isThrottled: Boolean,
+ isLockedOut: Boolean,
): MessageViewModel {
return MessageViewModel(
text = message ?: "",
- isUpdateAnimated = !isThrottled,
+ isUpdateAnimated = !isLockedOut,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 3b7e321..b682717 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -46,7 +46,7 @@
override val authenticationMethod = AuthenticationMethodModel.Password
- override val throttlingMessageId = R.string.kg_too_many_failed_password_attempts_dialog_message
+ override val lockoutMessageId = R.string.kg_too_many_failed_password_attempts_dialog_message
/** Whether the input method editor (for example, the software keyboard) is visible. */
private var isImeVisible: Boolean = false
@@ -56,13 +56,13 @@
/** Whether the UI should request focus on the text field element. */
val isTextFieldFocusRequested =
- combine(interactor.throttling, isTextFieldFocused) { throttling, hasFocus ->
+ combine(interactor.lockout, isTextFieldFocused) { throttling, hasFocus ->
throttling == null && !hasFocus
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = interactor.throttling.value == null && !isTextFieldFocused.value,
+ initialValue = interactor.lockout.value == null && !isTextFieldFocused.value,
)
override fun onHidden() {
@@ -104,7 +104,7 @@
* hidden.
*/
suspend fun onImeVisibilityChanged(isVisible: Boolean) {
- if (isImeVisible && !isVisible && interactor.throttling.value == null) {
+ if (isImeVisible && !isVisible && interactor.lockout.value == null) {
interactor.onImeHiddenByUser()
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index b1c5ab6..69f8032 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -80,7 +80,7 @@
override val authenticationMethod = AuthenticationMethodModel.Pattern
- override val throttlingMessageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message
+ override val lockoutMessageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message
/** Notifies that the user has started a drag gesture across the dot grid. */
fun onDragStart() {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index e25e82f..7f4a029 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -104,7 +104,7 @@
override val authenticationMethod: AuthenticationMethodModel = authenticationMethod
- override val throttlingMessageId = R.string.kg_too_many_failed_pin_attempts_dialog_message
+ override val lockoutMessageId = R.string.kg_too_many_failed_pin_attempts_dialog_message
init {
viewModelScope.launch { simBouncerInteractor.subId.collect { onResetSimFlow() } }
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt
index fdd98bec..3063ebd 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt
@@ -18,8 +18,12 @@
/** Models the bounds of the notification container. */
data class NotificationContainerBounds(
+ /** The position of the left of the container in its window coordinate system, in pixels. */
+ val left: Float = 0f,
/** The position of the top of the container in its window coordinate system, in pixels. */
val top: Float = 0f,
+ /** The position of the right of the container in its window coordinate system, in pixels. */
+ val right: Float = 0f,
/** The position of the bottom of the container in its window coordinate system, in pixels. */
val bottom: Float = 0f,
/** Whether any modifications to top/bottom should be smoothly animated. */
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 0405ca4..ca8268d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -82,6 +82,7 @@
import com.android.systemui.qs.QSFragmentStartableModule;
import com.android.systemui.qs.footer.dagger.FooterActionsModule;
import com.android.systemui.recents.Recents;
+import com.android.systemui.recordissue.RecordIssueModule;
import com.android.systemui.retail.dagger.RetailModeModule;
import com.android.systemui.scene.ui.view.WindowRootViewComponent;
import com.android.systemui.screenrecord.ScreenRecordModule;
@@ -209,6 +210,7 @@
PrivacyModule.class,
QRCodeScannerModule.class,
QSFragmentStartableModule.class,
+ RecordIssueModule.class,
ReferenceModule.class,
RetailModeModule.class,
ScreenshotModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java b/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
index 9c13a8c..3fac865 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
@@ -43,7 +43,7 @@
if (newState == DozeMachine.State.DOZE || newState == DozeMachine.State.DOZE_AOD) {
int currentUser = mSelectedUserInteractor.getSelectedUserId();
if (mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(currentUser)) {
- mKeyguardUpdateMonitor.clearBiometricRecognized();
+ mKeyguardUpdateMonitor.clearFingerprintRecognized();
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt
index 7b33e11..6cb68ba 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt
@@ -42,4 +42,6 @@
@FloatRange(from = 0.0, to = 1.0) val upperBookendScale: Float = 1f,
/** Vibration scale at the lower bookend of the slider */
@FloatRange(from = 0.0, to = 1.0) val lowerBookendScale: Float = 0.05f,
+ /** Exponent for power function compensation */
+ @FloatRange(from = 0.0, fromInclusive = false) val exponent: Float = 1f / 0.89f,
)
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
index f313fb3..9e6245a 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
@@ -21,9 +21,11 @@
import android.view.VelocityTracker
import android.view.animation.AccelerateInterpolator
import androidx.annotation.FloatRange
+import androidx.annotation.VisibleForTesting
import com.android.systemui.statusbar.VibratorHelper
import kotlin.math.abs
import kotlin.math.min
+import kotlin.math.pow
/**
* Listener of slider events that triggers haptic feedback.
@@ -63,18 +65,29 @@
* @param[absoluteVelocity] Velocity of the handle when it reached the bookend.
*/
private fun vibrateOnEdgeCollision(absoluteVelocity: Float) {
+ val powerScale = scaleOnEdgeCollision(absoluteVelocity)
+ val vibration =
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, powerScale)
+ .compose()
+ vibratorHelper.vibrate(vibration, VIBRATION_ATTRIBUTES_PIPELINING)
+ }
+
+ /**
+ * Get the velocity-based scale at the bookends
+ *
+ * @param[absoluteVelocity] Velocity of the handle when it reached the bookend.
+ * @return The power scale for the vibration.
+ */
+ @VisibleForTesting
+ fun scaleOnEdgeCollision(absoluteVelocity: Float): Float {
val velocityInterpolated =
velocityAccelerateInterpolator.getInterpolation(
min(absoluteVelocity / config.maxVelocityToScale, 1f)
)
val bookendScaleRange = config.upperBookendScale - config.lowerBookendScale
val bookendsHitScale = bookendScaleRange * velocityInterpolated + config.lowerBookendScale
-
- val vibration =
- VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, bookendsHitScale)
- .compose()
- vibratorHelper.vibrate(vibration, VIBRATION_ATTRIBUTES_PIPELINING)
+ return bookendsHitScale.pow(config.exponent)
}
/**
@@ -96,6 +109,31 @@
val deltaProgress = abs(normalizedSliderProgress - dragTextureLastProgress)
if (deltaProgress < config.deltaProgressForDragThreshold) return
+ val powerScale = scaleOnDragTexture(absoluteVelocity, normalizedSliderProgress)
+
+ // Trigger the vibration composition
+ val composition = VibrationEffect.startComposition()
+ repeat(config.numberOfLowTicks) {
+ composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, powerScale)
+ }
+ vibratorHelper.vibrate(composition.compose(), VIBRATION_ATTRIBUTES_PIPELINING)
+ dragTextureLastTime = currentTime
+ dragTextureLastProgress = normalizedSliderProgress
+ }
+
+ /**
+ * Get the scale of the drag texture vibration.
+ *
+ * @param[absoluteVelocity] Absolute velocity of the handle.
+ * @param[normalizedSliderProgress] Progress of the slider handled normalized to the range from
+ * 0F to 1F (inclusive).
+ * @return the scale of the vibration.
+ */
+ @VisibleForTesting
+ fun scaleOnDragTexture(
+ absoluteVelocity: Float,
+ @FloatRange(from = 0.0, to = 1.0) normalizedSliderProgress: Float
+ ): Float {
val velocityInterpolated =
velocityAccelerateInterpolator.getInterpolation(
min(absoluteVelocity / config.maxVelocityToScale, 1f)
@@ -113,15 +151,7 @@
// Total scale
val scale = positionBasedScale + velocityBasedScale
-
- // Trigger the vibration composition
- val composition = VibrationEffect.startComposition()
- repeat(config.numberOfLowTicks) {
- composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, scale)
- }
- vibratorHelper.vibrate(composition.compose(), VIBRATION_ATTRIBUTES_PIPELINING)
- dragTextureLastTime = currentTime
- dragTextureLastProgress = normalizedSliderProgress
+ return scale.pow(config.exponent)
}
override fun onHandleAcquiredByTouch() {}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 3009087..b7260f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2611,14 +2611,14 @@
}
if (mGoingToSleep) {
- mUpdateMonitor.clearBiometricRecognizedWhenKeyguardDone(currentUser);
+ mUpdateMonitor.clearFingerprintRecognizedWhenKeyguardDone(currentUser);
Log.i(TAG, "Device is going to sleep, aborting keyguardDone");
return;
}
setPendingLock(false); // user may have authenticated during the screen off animation
handleHide();
- mUpdateMonitor.clearBiometricRecognizedWhenKeyguardDone(currentUser);
+ mUpdateMonitor.clearFingerprintRecognizedWhenKeyguardDone(currentUser);
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index e47c448..eceaf6c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -326,7 +326,7 @@
it.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
},
)
- .flowOn(backgroundDispatcher)
+ .flowOn(mainDispatcher) // should revoke auth ASAP in the main thread
.onEach { anyOfThemIsTrue ->
if (anyOfThemIsTrue) {
clearPendingAuthRequest("Resetting auth status")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 0b6b971..7fdcf2f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -48,7 +48,7 @@
override fun start() {
listenForDreamingToOccluded()
listenForDreamingToGone()
- listenForDreamingToDozing()
+ listenForDreamingToAodOrDozing()
listenForTransitionToCamera(scope, keyguardInteractor)
}
@@ -94,7 +94,7 @@
}
}
- private fun listenForDreamingToDozing() {
+ private fun listenForDreamingToAodOrDozing() {
scope.launch {
combine(
keyguardInteractor.dozeTransitionModel,
@@ -102,11 +102,12 @@
::Pair
)
.collect { (dozeTransitionModel, keyguardState) ->
- if (
- dozeTransitionModel.to == DozeStateModel.DOZE &&
- keyguardState == KeyguardState.DREAMING
- ) {
- startTransitionTo(KeyguardState.DOZING)
+ if (keyguardState == KeyguardState.DREAMING) {
+ if (dozeTransitionModel.to == DozeStateModel.DOZE) {
+ startTransitionTo(KeyguardState.DOZING)
+ } else if (dozeTransitionModel.to == DozeStateModel.DOZE_AOD) {
+ startTransitionTo(KeyguardState.AOD)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
index 5ed70b5..046916a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
@@ -78,6 +78,9 @@
* flows.
*/
interface FaceAuthenticationListener {
+ /** Receive face isAuthenticated updates */
+ fun onAuthenticatedChanged(isAuthenticated: Boolean)
+
/** Receive face authentication status updates */
fun onAuthenticationStatusChanged(status: FaceAuthenticationStatus)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 448411e..7882a97 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -46,6 +46,7 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -66,6 +67,7 @@
@Inject
constructor(
private val keyguardInteractor: KeyguardInteractor,
+ private val shadeInteractor: ShadeInteractor,
private val lockPatternUtils: LockPatternUtils,
private val keyguardStateController: KeyguardStateController,
private val userTracker: UserTracker,
@@ -100,9 +102,10 @@
quickAffordanceAlwaysVisible(position),
keyguardInteractor.isDozing,
keyguardInteractor.isKeyguardShowing,
+ shadeInteractor.anyExpansion,
biometricSettingsRepository.isCurrentUserInLockdown,
- ) { affordance, isDozing, isKeyguardShowing, isUserInLockdown ->
- if (!isDozing && isKeyguardShowing && !isUserInLockdown) {
+ ) { affordance, isDozing, isKeyguardShowing, qsExpansion, isUserInLockdown ->
+ if (!isDozing && isKeyguardShowing && (qsExpansion < 1.0f) && !isUserInLockdown) {
affordance
} else {
KeyguardQuickAffordanceModel.Hidden
@@ -117,10 +120,14 @@
* This is useful for experiences like the lock screen preview mode, where the affordances must
* always be visible.
*/
- fun quickAffordanceAlwaysVisible(
+ suspend fun quickAffordanceAlwaysVisible(
position: KeyguardQuickAffordancePosition,
): Flow<KeyguardQuickAffordanceModel> {
- return quickAffordanceInternal(position)
+ return if (isFeatureDisabledByDevicePolicy()) {
+ flowOf(KeyguardQuickAffordanceModel.Hidden)
+ } else {
+ quickAffordanceInternal(position)
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
index 532df4a..fb20000 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
@@ -16,8 +16,10 @@
package com.android.systemui.keyguard.domain.interactor
+import android.app.trust.TrustManager
import android.content.Context
import android.hardware.biometrics.BiometricFaceConstants
+import android.hardware.biometrics.BiometricSourceType
import com.android.keyguard.FaceAuthUiEvent
import com.android.keyguard.FaceWakeUpTriggersConfig
import com.android.keyguard.KeyguardUpdateMonitor
@@ -83,6 +85,7 @@
private val faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig,
private val powerInteractor: PowerInteractor,
private val biometricSettingsRepository: BiometricSettingsRepository,
+ private val trustManager: TrustManager,
) : CoreStartable, KeyguardFaceAuthInteractor {
private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf()
@@ -291,6 +294,20 @@
.onEach { running -> listeners.forEach { it.onRunningStateChanged(running) } }
.flowOn(mainDispatcher)
.launchIn(applicationScope)
+ repository.isAuthenticated
+ .sample(userRepository.selectedUserInfo, ::Pair)
+ .onEach { (isAuthenticated, userInfo) ->
+ if (!isAuthenticated) {
+ faceAuthenticationLogger.clearFaceRecognized()
+ trustManager.clearAllBiometricRecognized(BiometricSourceType.FACE, userInfo.id)
+ }
+ }
+ .flowOn(backgroundDispatcher)
+ .onEach { (isAuthenticated, _) ->
+ listeners.forEach { it.onAuthenticatedChanged(isAuthenticated) }
+ }
+ .flowOn(mainDispatcher)
+ .launchIn(applicationScope)
biometricSettingsRepository.isFaceAuthEnrolledAndEnabled
.onEach { enrolledAndEnabled ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
index a64a422..e7b6e44 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
@@ -26,7 +26,6 @@
import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.res.R
@@ -92,13 +91,7 @@
connect(R.id.nssl_placeholder, START, PARENT_ID, START)
connect(R.id.nssl_placeholder, END, PARENT_ID, END)
- val lockId =
- if (DeviceEntryUdfpsRefactor.isEnabled) {
- R.id.device_entry_icon_view
- } else {
- R.id.lock_icon_view
- }
- connect(R.id.nssl_placeholder, BOTTOM, lockId, TOP)
+ addNotificationPlaceholderBarrier(this)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
index a25471c..400d0dc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
@@ -20,7 +20,12 @@
import android.content.Context
import android.view.View
import android.view.ViewGroup
+import androidx.constraintlayout.widget.Barrier
import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.res.R
@@ -54,6 +59,29 @@
private val placeHolderId = R.id.nssl_placeholder
private var disposableHandle: DisposableHandle? = null
+ /**
+ * Align the notification placeholder bottom to the top of either the lock icon or the ambient
+ * indication area, whichever is higher.
+ */
+ protected fun addNotificationPlaceholderBarrier(constraintSet: ConstraintSet) {
+ val lockId =
+ if (DeviceEntryUdfpsRefactor.isEnabled) {
+ R.id.device_entry_icon_view
+ } else {
+ R.id.lock_icon_view
+ }
+
+ constraintSet.apply {
+ createBarrier(
+ R.id.nssl_placeholder_barrier_bottom,
+ Barrier.TOP,
+ 0,
+ *intArrayOf(lockId, R.id.ambient_indication_container)
+ )
+ connect(R.id.nssl_placeholder, BOTTOM, R.id.nssl_placeholder_barrier_bottom, TOP)
+ }
+ }
+
override fun addViews(constraintLayout: ConstraintLayout) {
if (!KeyguardShadeMigrationNssl.isEnabled) {
return
@@ -85,9 +113,9 @@
)
if (sceneContainerFlags.flexiNotifsEnabled()) {
NotificationStackAppearanceViewBinder.bind(
+ context,
sharedNotificationContainer,
notificationStackAppearanceViewModel,
- sceneContainerFlags,
ambientState,
controller,
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
index f5963be..b0b5c81 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
@@ -19,14 +19,12 @@
import android.content.Context
import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.END
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.res.R
@@ -97,13 +95,7 @@
connect(R.id.nssl_placeholder, START, PARENT_ID, START)
connect(R.id.nssl_placeholder, END, PARENT_ID, END)
- val lockId =
- if (DeviceEntryUdfpsRefactor.isEnabled) {
- R.id.device_entry_icon_view
- } else {
- R.id.lock_icon_view
- }
- connect(R.id.nssl_placeholder, BOTTOM, lockId, TOP)
+ addNotificationPlaceholderBarrier(this)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 4588e02..1d4520f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -111,10 +111,21 @@
/** An observable for the alpha level for the entire keyguard root view. */
val alpha: Flow<Float> =
- merge(
- keyguardInteractor.keyguardAlpha.distinctUntilChanged(),
- occludedToLockscreenTransitionViewModel.lockscreenAlpha,
- )
+ combine(
+ keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) },
+ merge(
+ keyguardInteractor.keyguardAlpha,
+ occludedToLockscreenTransitionViewModel.lockscreenAlpha,
+ )
+ ) { transitionToGone, alpha ->
+ if (transitionToGone == 1f) {
+ // Ensures content is not visible when in GONE state
+ 0f
+ } else {
+ alpha
+ }
+ }
+ .distinctUntilChanged()
private fun burnIn(): Flow<BurnInModel> {
val dozingAmount: Flow<Float> =
@@ -229,7 +240,9 @@
.distinctUntilChanged()
fun onNotificationContainerBoundsChanged(top: Float, bottom: Float) {
- keyguardInteractor.setNotificationContainerBounds(NotificationContainerBounds(top, bottom))
+ keyguardInteractor.setNotificationContainerBounds(
+ NotificationContainerBounds(top = top, bottom = bottom)
+ )
}
/** Is there an expanded pulse, are we animating in response? */
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
index 8c5690b..3c2facb 100644
--- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -132,6 +132,10 @@
logBuffer.log(TAG, DEBUG, "Face authentication failed")
}
+ fun clearFaceRecognized() {
+ logBuffer.log(TAG, DEBUG, "Clear face recognized")
+ }
+
fun authenticationError(
errorCode: Int,
errString: CharSequence?,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
new file mode 100644
index 0000000..a4088f8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles
+
+import android.content.Intent
+import android.os.Handler
+import android.os.Looper
+import android.service.quicksettings.Tile
+import android.text.TextUtils
+import android.view.View
+import android.widget.Switch
+import androidx.annotation.VisibleForTesting
+import com.android.internal.logging.MetricsLogger
+import com.android.systemui.Flags.recordIssueQsTile
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+class RecordIssueTile
+@Inject
+constructor(
+ host: QSHost,
+ uiEventLogger: QsEventLogger,
+ @Background backgroundLooper: Looper,
+ @Main mainHandler: Handler,
+ falsingManager: FalsingManager,
+ metricsLogger: MetricsLogger,
+ statusBarStateController: StatusBarStateController,
+ activityStarter: ActivityStarter,
+ qsLogger: QSLogger
+) :
+ QSTileImpl<QSTile.BooleanState>(
+ host,
+ uiEventLogger,
+ backgroundLooper,
+ mainHandler,
+ falsingManager,
+ metricsLogger,
+ statusBarStateController,
+ activityStarter,
+ qsLogger
+ ) {
+
+ @VisibleForTesting var isRecording: Boolean = false
+
+ override fun getTileLabel(): CharSequence = mContext.getString(R.string.qs_record_issue_label)
+
+ override fun isAvailable(): Boolean = recordIssueQsTile()
+
+ override fun newTileState(): QSTile.BooleanState =
+ QSTile.BooleanState().apply {
+ label = tileLabel
+ handlesLongClick = false
+ }
+
+ override fun handleClick(view: View?) {
+ isRecording = !isRecording
+ refreshState()
+ }
+
+ override fun getLongClickIntent(): Intent? = null
+
+ @VisibleForTesting
+ public override fun handleUpdateState(qsTileState: QSTile.BooleanState, arg: Any?) {
+ qsTileState.apply {
+ if (isRecording) {
+ value = true
+ state = Tile.STATE_ACTIVE
+ forceExpandIcon = false
+ secondaryLabel = mContext.getString(R.string.qs_record_issue_stop)
+ icon = ResourceIcon.get(R.drawable.qs_record_issue_icon_on)
+ } else {
+ value = false
+ state = Tile.STATE_INACTIVE
+ forceExpandIcon = true
+ secondaryLabel = mContext.getString(R.string.qs_record_issue_start)
+ icon = ResourceIcon.get(R.drawable.qs_record_issue_icon_off)
+ }
+ label = tileLabel
+ contentDescription =
+ if (TextUtils.isEmpty(secondaryLabel)) label else "$label, $secondaryLabel"
+ expandedAccessibilityClassName = Switch::class.java.name
+ }
+ }
+
+ companion object {
+ const val TILE_SPEC = "record_issue"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt
new file mode 100644
index 0000000..fc42ba4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.saver.domain
+
+import android.content.Context
+import android.content.DialogInterface
+import android.content.SharedPreferences
+import android.os.Bundle
+import com.android.internal.R
+import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileUserActionInteractor
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.DataSaverController
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+class DataSaverDialogDelegate(
+ private val sysuiDialogFactory: SystemUIDialog.Factory,
+ private val context: Context,
+ private val backgroundContext: CoroutineContext,
+ private val dataSaverController: DataSaverController,
+ private val sharedPreferences: SharedPreferences,
+) : SystemUIDialog.Delegate {
+ override fun createDialog(): SystemUIDialog {
+ return sysuiDialogFactory.create(this, context)
+ }
+
+ override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+ with(dialog) {
+ setTitle(R.string.data_saver_enable_title)
+ setMessage(R.string.data_saver_description)
+ setPositiveButton(R.string.data_saver_enable_button) { _: DialogInterface?, _ ->
+ CoroutineScope(backgroundContext).launch {
+ dataSaverController.setDataSaverEnabled(true)
+ }
+
+ sharedPreferences
+ .edit()
+ .putBoolean(DataSaverTileUserActionInteractor.DIALOG_SHOWN, true)
+ .apply()
+ }
+ setNeutralButton(R.string.cancel, null)
+ setShowForAllUsers(true)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
new file mode 100644
index 0000000..25b0913
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.saver.domain
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [DataSaverTileModel] to [QSTileState]. */
+class DataSaverTileMapper @Inject constructor(@Main private val resources: Resources) :
+ QSTileDataToStateMapper<DataSaverTileModel> {
+ override fun map(config: QSTileConfig, data: DataSaverTileModel): QSTileState =
+ QSTileState.build(resources, config.uiConfig) {
+ with(data) {
+ if (isEnabled) {
+ activationState = QSTileState.ActivationState.ACTIVE
+ icon = { Icon.Resource(R.drawable.qs_data_saver_icon_on, null) }
+ secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[2]
+ } else {
+ activationState = QSTileState.ActivationState.INACTIVE
+ icon = { Icon.Resource(R.drawable.qs_data_saver_icon_off, null) }
+ secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[1]
+ }
+ contentDescription = label
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractor.kt
new file mode 100644
index 0000000..91e049b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractor.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.saver.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
+import com.android.systemui.statusbar.policy.DataSaverController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/** Observes data saver state changes providing the [DataSaverTileModel]. */
+class DataSaverTileDataInteractor
+@Inject
+constructor(
+ private val dataSaverController: DataSaverController,
+) : QSTileDataInteractor<DataSaverTileModel> {
+
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<DataSaverTileModel> =
+ ConflatedCallbackFlow.conflatedCallbackFlow {
+ val initialValue = dataSaverController.isDataSaverEnabled
+ trySend(DataSaverTileModel(initialValue))
+
+ val callback = DataSaverController.Listener { trySend(DataSaverTileModel(it)) }
+
+ dataSaverController.addCallback(callback)
+ awaitClose { dataSaverController.removeCallback(callback) }
+ }
+
+ override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt
new file mode 100644
index 0000000..af74409
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.saver.domain.interactor
+
+import android.content.Context
+import android.content.Intent
+import android.provider.Settings
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+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.saver.domain.DataSaverDialogDelegate
+import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.DataSaverController
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
+
+/** Handles data saver tile clicks. */
+class DataSaverTileUserActionInteractor
+@Inject
+constructor(
+ @Application private val context: Context,
+ @Main private val coroutineContext: CoroutineContext,
+ @Background private val backgroundContext: CoroutineContext,
+ private val dataSaverController: DataSaverController,
+ private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+ private val dialogLaunchAnimator: DialogLaunchAnimator,
+ private val systemUIDialogFactory: SystemUIDialog.Factory,
+ userFileManager: UserFileManager,
+) : QSTileUserActionInteractor<DataSaverTileModel> {
+ companion object {
+ private const val INTERACTION_JANK_TAG = "start_data_saver"
+ const val PREFS = "data_saver"
+ const val DIALOG_SHOWN = "data_saver_dialog_shown"
+ }
+
+ val sharedPreferences =
+ userFileManager.getSharedPreferences(PREFS, Context.MODE_PRIVATE, context.userId)
+
+ override suspend fun handleInput(input: QSTileInput<DataSaverTileModel>): Unit =
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> {
+ val wasEnabled: Boolean = data.isEnabled
+ if (wasEnabled || sharedPreferences.getBoolean(DIALOG_SHOWN, false)) {
+ withContext(backgroundContext) {
+ dataSaverController.setDataSaverEnabled(!wasEnabled)
+ }
+ return@with
+ }
+ // Show a dialog to confirm first. Dialogs shown by the DialogLaunchAnimator
+ // must be created and shown on the main thread, so we post it to the UI
+ // handler
+ withContext(coroutineContext) {
+ val dialogContext = action.view?.context ?: context
+ val dialogDelegate =
+ DataSaverDialogDelegate(
+ systemUIDialogFactory,
+ dialogContext,
+ backgroundContext,
+ dataSaverController,
+ sharedPreferences
+ )
+ val dialog = systemUIDialogFactory.create(dialogDelegate, dialogContext)
+
+ if (action.view != null) {
+ dialogLaunchAnimator.showFromView(
+ dialog,
+ action.view!!,
+ DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG
+ )
+ )
+ } else {
+ dialog.show()
+ }
+ }
+ }
+ is QSTileUserAction.LongClick -> {
+ qsTileIntentUserActionHandler.handle(
+ action.view,
+ Intent(Settings.ACTION_DATA_SAVER_SETTINGS)
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/model/DataSaverTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/model/DataSaverTileModel.kt
new file mode 100644
index 0000000..040c7bf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/model/DataSaverTileModel.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.saver.domain.model
+
+/**
+ * data saver tile model.
+ *
+ * @param isEnabled is true when the data saver is enabled;
+ */
+@JvmInline value class DataSaverTileModel(val isEnabled: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt
new file mode 100644
index 0000000..d67cf4d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recordissue
+
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tiles.RecordIssueTile
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+
+@Module
+interface RecordIssueModule {
+ /** Inject RecordIssueTile into tileMap in QSModule */
+ @Binds
+ @IntoMap
+ @StringKey(RecordIssueTile.TILE_SPEC)
+ fun bindRecordIssueTile(recordIssueTile: RecordIssueTile): QSTileImpl<*>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index e9779cd..5fbb60d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -59,6 +59,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.scene.ui.view.WindowRootViewComponent;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
@@ -116,6 +117,7 @@
private final AuthController mAuthController;
private final Lazy<SelectedUserInteractor> mUserInteractor;
private final Lazy<ShadeInteractor> mShadeInteractorLazy;
+ private final SceneContainerFlags mSceneContainerFlags;
private ViewGroup mWindowRootView;
private LayoutParams mLp;
private boolean mHasTopUi;
@@ -162,7 +164,8 @@
Lazy<ShadeInteractor> shadeInteractorLazy,
ShadeWindowLogger logger,
Lazy<SelectedUserInteractor> userInteractor,
- UserTracker userTracker) {
+ UserTracker userTracker,
+ SceneContainerFlags sceneContainerFlags) {
mContext = context;
mWindowRootViewComponentFactory = windowRootViewComponentFactory;
mWindowManager = windowManager;
@@ -180,6 +183,7 @@
dumpManager.registerDumpable(this);
mAuthController = authController;
mUserInteractor = userInteractor;
+ mSceneContainerFlags = sceneContainerFlags;
mLastKeyguardRotationAllowed = mKeyguardStateController.isKeyguardScreenRotationAllowed();
mLockScreenDisplayTimeout = context.getResources()
.getInteger(R.integer.config_lockScreenDisplayTimeout);
@@ -287,6 +291,15 @@
mLp.privateFlags |= PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
mLp.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+ if (mSceneContainerFlags.isEnabled()) {
+ // This prevents the appearance and disappearance of the software keyboard (also known
+ // as the "IME") from scrolling/panning the window to make room for the keyboard.
+ //
+ // The scene container logic does its own adjustment and animation when the IME appears
+ // or disappears.
+ mLp.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+ }
+
mWindowManager.addView(mWindowRootView, mLp);
mLpChanged.copyFrom(mLp);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
index a3adea0..642eacc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
@@ -35,6 +35,10 @@
import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileDataInteractor
import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel
+import com.android.systemui.qs.tiles.impl.saver.domain.DataSaverTileMapper
+import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileDataInteractor
+import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy
import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
@@ -85,6 +89,7 @@
companion object {
const val AIRPLANE_MODE_TILE_SPEC = "airplane"
+ const val DATA_SAVER_TILE_SPEC = "saver"
/** Inject InternetTile or InternetTileNewImpl into tileMap in QSModule */
@Provides
@@ -132,5 +137,36 @@
stateInteractor,
mapper,
)
+
+ @Provides
+ @IntoMap
+ @StringKey(DATA_SAVER_TILE_SPEC)
+ fun provideDataSaverTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ QSTileConfig(
+ tileSpec = TileSpec.create(DATA_SAVER_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.qs_data_saver_icon_off,
+ labelRes = R.string.data_saver,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ )
+
+ /** Inject DataSaverTile into tileViewModelMap in QSModule */
+ @Provides
+ @IntoMap
+ @StringKey(DATA_SAVER_TILE_SPEC)
+ fun provideDataSaverTileViewModel(
+ factory: QSTileViewModelFactory.Static<DataSaverTileModel>,
+ mapper: DataSaverTileMapper,
+ stateInteractor: DataSaverTileDataInteractor,
+ userActionInteractor: DataSaverTileUserActionInteractor
+ ): QSTileViewModel =
+ factory.create(
+ TileSpec.create(DATA_SAVER_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
index abf09ae..e78a694 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
@@ -26,5 +26,8 @@
@SysUISingleton
class NotificationStackAppearanceRepository @Inject constructor() {
/** The bounds of the notification stack in the current scene. */
- val stackBounds = MutableStateFlow(NotificationContainerBounds(0f, 0f))
+ val stackBounds = MutableStateFlow(NotificationContainerBounds())
+
+ /** The corner radius of the notification stack, in dp. */
+ val cornerRadiusDp = MutableStateFlow(32f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index 32e4e89..61a4dfc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -39,4 +39,7 @@
check(bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" }
repository.stackBounds.value = bounds
}
+
+ /** The corner radius of the notification stack, in dp. */
+ val cornerRadiusDp: StateFlow<Float> = repository.cornerRadiusDp.asStateFlow()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
index fa7a8fd..a9b542d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
@@ -16,14 +16,16 @@
package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+import android.content.Context
+import android.util.TypedValue
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.statusbar.notification.stack.AmbientState
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel
+import kotlin.math.roundToInt
import kotlinx.coroutines.launch
/** Binds the shared notification container to its view-model. */
@@ -31,9 +33,9 @@
@JvmStatic
fun bind(
+ context: Context,
view: SharedNotificationContainer,
viewModel: NotificationStackAppearanceViewModel,
- sceneContainerFlags: SceneContainerFlags,
ambientState: AmbientState,
controller: NotificationStackScrollLayoutController,
) {
@@ -45,6 +47,14 @@
bounds.top,
controller.isAddOrRemoveAnimationPending
)
+ controller.setRoundedClippingBounds(
+ it.left,
+ it.top,
+ it.right,
+ it.bottom,
+ viewModel.cornerRadiusDp.value.dpToPx(context),
+ viewModel.cornerRadiusDp.value.dpToPx(context),
+ )
}
}
launch {
@@ -56,4 +66,13 @@
}
}
}
+
+ private fun Float.dpToPx(context: Context): Int {
+ return TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ this,
+ context.resources.displayMetrics
+ )
+ .roundToInt()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
index f4c0e92..834d3ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
@@ -23,6 +23,7 @@
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
/** ViewModel which represents the state of the NSSL/Controller in the world of flexiglass */
@SysUISingleton
@@ -37,4 +38,7 @@
/** The bounds of the notification stack in the current scene. */
val stackBounds: Flow<NotificationContainerBounds> = stackAppearanceInteractor.stackBounds
+
+ /** The corner radius of the notification stack, in dp. */
+ val cornerRadiusDp: StateFlow<Float> = stackAppearanceInteractor.cornerRadiusDp
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index c6fd98e..9f22118 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -24,6 +24,7 @@
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
/**
* ViewModel used by the Notification placeholders inside the scene container to update the
@@ -55,9 +56,14 @@
* pixels.
*/
fun onBoundsChanged(
+ left: Float,
top: Float,
+ right: Float,
bottom: Float,
) {
- interactor.setStackBounds(NotificationContainerBounds(top, bottom))
+ interactor.setStackBounds(NotificationContainerBounds(left, top, right, bottom))
}
+
+ /** The corner radius of the placeholder, in dp. */
+ val cornerRadiusDp: StateFlow<Float> = interactor.cornerRadiusDp
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 5b854e6..9594bc3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -177,8 +177,8 @@
}
.stateIn(
scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = NotificationContainerBounds(0f, 0f),
+ started = SharingStarted.Lazily,
+ initialValue = NotificationContainerBounds(),
)
val alpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
index 99b123f..ae58398 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
@@ -161,13 +161,13 @@
if (it == null) {
notConnectedFlow
} else {
- val secondary = it.contentDescription.toString()
+ val secondary = it.contentDescription
flowOf(
InternetTileModel.Active(
- secondaryTitle = secondary,
+ secondaryLabel = secondary?.toText(),
iconId = it.res,
stateDescription = null,
- contentDescription = ContentDescription.Loaded(secondary),
+ contentDescription = secondary,
)
)
}
@@ -241,5 +241,11 @@
string.substring(1, length - 1)
} else string
}
+
+ private fun ContentDescription.toText(): Text =
+ when (this) {
+ is ContentDescription.Loaded -> Text.Loaded(this.description)
+ is ContentDescription.Resource -> Text.Resource(this.res)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 80c6802..756c440 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -484,7 +484,12 @@
}
@Override
- public void onBiometricsCleared() {
+ public void onFingerprintsCleared() {
+ update(false /* alwaysUpdate */);
+ }
+
+ @Override
+ public void onFacesCleared() {
update(false /* alwaysUpdate */);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
index 395d712..ca95822 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
@@ -18,10 +18,10 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SIDE_BY_SIDE
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SPLIT
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STACKED
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STANDARD
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.BELOW_USER_SWITCHER
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.BESIDE_USER_SWITCHER
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SPLIT_BOUNCER
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STANDARD_BOUNCER
import com.google.common.truth.Truth.assertThat
import java.util.Locale
import org.junit.Test
@@ -84,33 +84,33 @@
listOf(
Phone to
Expected(
- whenNaturallyHeld = STANDARD,
- whenUnnaturallyHeld = SPLIT,
+ whenNaturallyHeld = STANDARD_BOUNCER,
+ whenUnnaturallyHeld = SPLIT_BOUNCER,
),
Tablet to
Expected(
- whenNaturallyHeld = SIDE_BY_SIDE,
- whenUnnaturallyHeld = STACKED,
+ whenNaturallyHeld = BESIDE_USER_SWITCHER,
+ whenUnnaturallyHeld = BELOW_USER_SWITCHER,
),
Folded to
Expected(
- whenNaturallyHeld = STANDARD,
- whenUnnaturallyHeld = SPLIT,
+ whenNaturallyHeld = STANDARD_BOUNCER,
+ whenUnnaturallyHeld = SPLIT_BOUNCER,
),
Unfolded to
Expected(
- whenNaturallyHeld = SIDE_BY_SIDE,
- whenUnnaturallyHeld = STANDARD,
+ whenNaturallyHeld = BESIDE_USER_SWITCHER,
+ whenUnnaturallyHeld = STANDARD_BOUNCER,
),
TallerFolded to
Expected(
- whenNaturallyHeld = STANDARD,
- whenUnnaturallyHeld = SPLIT,
+ whenNaturallyHeld = STANDARD_BOUNCER,
+ whenUnnaturallyHeld = SPLIT_BOUNCER,
),
TallerUnfolded to
Expected(
- whenNaturallyHeld = SIDE_BY_SIDE,
- whenUnnaturallyHeld = SIDE_BY_SIDE,
+ whenNaturallyHeld = BESIDE_USER_SWITCHER,
+ whenUnnaturallyHeld = BESIDE_USER_SWITCHER,
),
)
.flatMap { (device, expected) ->
@@ -124,13 +124,13 @@
)
)
- if (expected.whenNaturallyHeld == SIDE_BY_SIDE) {
+ if (expected.whenNaturallyHeld == BESIDE_USER_SWITCHER) {
add(
TestCase(
device = device,
held = device.naturallyHeld,
isSideBySideSupported = false,
- expected = STANDARD,
+ expected = STANDARD_BOUNCER,
)
)
}
@@ -144,13 +144,13 @@
)
)
- if (expected.whenUnnaturallyHeld == SIDE_BY_SIDE) {
+ if (expected.whenUnnaturallyHeld == BESIDE_USER_SWITCHER) {
add(
TestCase(
device = device,
held = device.naturallyHeld.flip(),
isSideBySideSupported = false,
- expected = STANDARD,
+ expected = STANDARD_BOUNCER,
)
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
index 7750d25..ab6bc2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
@@ -71,7 +71,7 @@
VibrationEffect.startComposition()
.addPrimitive(
VibrationEffect.Composition.PRIMITIVE_CLICK,
- scaleAtBookends(config.maxVelocityToScale)
+ sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
)
.compose()
@@ -86,7 +86,7 @@
VibrationEffect.startComposition()
.addPrimitive(
VibrationEffect.Composition.PRIMITIVE_CLICK,
- scaleAtBookends(config.maxVelocityToScale)
+ sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale)
)
.compose()
@@ -102,7 +102,7 @@
VibrationEffect.startComposition()
.addPrimitive(
VibrationEffect.Composition.PRIMITIVE_CLICK,
- scaleAtBookends(config.maxVelocityToScale)
+ sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
)
.compose()
@@ -117,7 +117,7 @@
VibrationEffect.startComposition()
.addPrimitive(
VibrationEffect.Composition.PRIMITIVE_CLICK,
- scaleAtBookends(config.maxVelocityToScale)
+ sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
)
.compose()
@@ -132,7 +132,11 @@
fun playHapticAtProgress_onQuickSuccession_playsLowTicksOnce() {
// GIVEN max velocity and slider progress
val progress = 1f
- val expectedScale = scaleAtProgressChange(config.maxVelocityToScale.toFloat(), progress)
+ val expectedScale =
+ sliderHapticFeedbackProvider.scaleOnDragTexture(
+ config.maxVelocityToScale,
+ progress,
+ )
val ticks = VibrationEffect.startComposition()
repeat(config.numberOfLowTicks) {
ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
@@ -203,7 +207,11 @@
fun playHapticAtLowerBookend_afterPlayingAtProgress_playsTwice() {
// GIVEN max velocity and slider progress
val progress = 1f
- val expectedScale = scaleAtProgressChange(config.maxVelocityToScale.toFloat(), progress)
+ val expectedScale =
+ sliderHapticFeedbackProvider.scaleOnDragTexture(
+ config.maxVelocityToScale,
+ progress,
+ )
val ticks = VibrationEffect.startComposition()
repeat(config.numberOfLowTicks) {
ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
@@ -212,7 +220,7 @@
VibrationEffect.startComposition()
.addPrimitive(
VibrationEffect.Composition.PRIMITIVE_CLICK,
- scaleAtBookends(config.maxVelocityToScale)
+ sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
)
.compose()
@@ -232,7 +240,11 @@
fun playHapticAtUpperBookend_afterPlayingAtProgress_playsTwice() {
// GIVEN max velocity and slider progress
val progress = 1f
- val expectedScale = scaleAtProgressChange(config.maxVelocityToScale.toFloat(), progress)
+ val expectedScale =
+ sliderHapticFeedbackProvider.scaleOnDragTexture(
+ config.maxVelocityToScale,
+ progress,
+ )
val ticks = VibrationEffect.startComposition()
repeat(config.numberOfLowTicks) {
ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
@@ -241,7 +253,7 @@
VibrationEffect.startComposition()
.addPrimitive(
VibrationEffect.Composition.PRIMITIVE_CLICK,
- scaleAtBookends(config.maxVelocityToScale)
+ sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
)
.compose()
@@ -289,28 +301,12 @@
assertEquals(-1f, sliderHapticFeedbackProvider.dragTextureLastProgress)
}
- private fun scaleAtBookends(velocity: Float): Float {
- val range = config.upperBookendScale - config.lowerBookendScale
- val interpolatedVelocity =
- velocityInterpolator.getInterpolation(velocity / config.maxVelocityToScale)
- return interpolatedVelocity * range + config.lowerBookendScale
- }
-
- private fun scaleAtProgressChange(velocity: Float, progress: Float): Float {
- val range = config.progressBasedDragMaxScale - config.progressBasedDragMinScale
- val interpolatedVelocity =
- velocityInterpolator.getInterpolation(velocity / config.maxVelocityToScale)
- val interpolatedProgress = progressInterpolator.getInterpolation(progress)
- val bump = interpolatedVelocity * config.additionalVelocityMaxBump
- return interpolatedProgress * range + config.progressBasedDragMinScale + bump
- }
-
private fun generateTicksComposition(velocity: Float, progress: Float): VibrationEffect {
val ticks = VibrationEffect.startComposition()
repeat(config.numberOfLowTicks) {
ticks.addPrimitive(
VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
- scaleAtProgressChange(velocity, progress)
+ sliderHapticFeedbackProvider.scaleOnDragTexture(velocity, progress),
)
}
return ticks.compose()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index 40c9432..076d725 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -53,9 +53,11 @@
import com.android.systemui.res.R
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
@@ -101,6 +103,8 @@
private lateinit var underTest: CustomizationProvider
private lateinit var testScope: TestScope
+ private val kosmos = testKosmos()
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -185,6 +189,7 @@
},
)
.keyguardInteractor,
+ shadeInteractor = kosmos.shadeInteractor,
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
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 d246f0e..ae5f625 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -97,6 +97,7 @@
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.scene.FakeWindowRootViewComponent;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.scene.ui.view.WindowRootView;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
@@ -214,6 +215,7 @@
private @Mock CoroutineDispatcher mDispatcher;
private @Mock DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel;
private @Mock SystemPropertiesHelper mSystemPropertiesHelper;
+ private @Mock SceneContainerFlags mSceneContainerFlags;
private FakeFeatureFlags mFeatureFlags;
private final int mDefaultUserId = 100;
@@ -258,7 +260,8 @@
() -> mShadeInteractor,
mShadeWindowLogger,
() -> mSelectedUserInteractor,
- mUserTracker);
+ mUserTracker,
+ mSceneContainerFlags);
mFeatureFlags = new FakeFeatureFlags();
mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
mFeatureFlags.set(Flags.REFACTOR_GETCURRENTUSER, true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index de12b8f..4ab8e28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -17,8 +17,10 @@
package com.android.systemui.keyguard.domain.interactor
+import android.app.trust.TrustManager
import android.content.pm.UserInfo
import android.hardware.biometrics.BiometricFaceConstants
+import android.hardware.biometrics.BiometricSourceType
import android.os.Handler
import android.os.PowerManager
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -62,6 +64,7 @@
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -74,8 +77,11 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@@ -99,7 +105,8 @@
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig
- @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
+ @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
+ @Mock private lateinit var trustManager: TrustManager
@Before
fun setup() {
@@ -146,7 +153,7 @@
keyguardUpdateMonitor,
FakeTrustRepository(),
testScope.backgroundScope,
- mSelectedUserInteractor,
+ selectedUserInteractor,
underTest,
)
},
@@ -169,6 +176,7 @@
faceWakeUpTriggersConfig,
powerInteractor,
fakeBiometricSettingsRepository,
+ trustManager,
)
}
@@ -498,6 +506,22 @@
assertThat(faceAuthRepository.isLockedOut.value).isTrue()
}
+ @Test
+ fun whenIsAuthenticatedFalse_clearFaceBiometrics() =
+ testScope.runTest {
+ underTest.start()
+
+ faceAuthRepository.isAuthenticated.value = true
+ runCurrent()
+ verify(trustManager, never())
+ .clearAllBiometricRecognized(eq(BiometricSourceType.FACE), anyInt())
+
+ faceAuthRepository.isAuthenticated.value = false
+ runCurrent()
+
+ verify(trustManager).clearAllBiometricRecognized(eq(BiometricSourceType.FACE), anyInt())
+ }
+
companion object {
private const val primaryUserId = 1
private val primaryUser = UserInfo(primaryUserId, "test user", UserInfo.FLAG_PRIMARY)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 66c8a22..b4ae7e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -46,7 +46,9 @@
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
@@ -242,6 +244,8 @@
private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
private lateinit var userTracker: UserTracker
+ private val kosmos = testKosmos()
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -311,6 +315,7 @@
featureFlags = featureFlags,
)
.keyguardInteractor,
+ shadeInteractor = kosmos.shadeInteractor,
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 976dc5f..b8a8bdf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -1137,7 +1137,7 @@
runCurrent()
// WHEN primary bouncer shows
- bouncerRepository.setPrimaryShow(true) // beverlyt
+ bouncerRepository.setPrimaryShow(true)
runCurrent()
val info =
@@ -1232,6 +1232,36 @@
}
@Test
+ fun dreamingToAod() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to DREAMING
+ keyguardRepository.setDreaming(true)
+ runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING)
+ runCurrent()
+
+ // WHEN the device starts DOZE_AOD
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(
+ from = DozeStateModel.INITIALIZED,
+ to = DozeStateModel.DOZE_AOD,
+ )
+ )
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ // THEN a transition to AOD should occur
+ assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
+ assertThat(info.to).isEqualTo(KeyguardState.AOD)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun lockscreenToOccluded() =
testScope.runTest {
// GIVEN a prior transition has run to LOCKSCREEN
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 1584be0..af38523c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -54,9 +54,11 @@
import com.android.systemui.res.R
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
@@ -108,6 +110,8 @@
private lateinit var dockManager: DockManagerFake
private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+ private val kosmos = testKosmos()
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -221,6 +225,7 @@
quickAffordanceInteractor =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = keyguardInteractor,
+ shadeInteractor = kosmos.shadeInteractor,
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 0c30d10..b6a661b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -264,12 +264,14 @@
whenever(lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha)
.thenReturn(emptyFlow())
whenever(shadeInteractor.qsExpansion).thenReturn(intendedShadeAlphaMutableStateFlow)
+ whenever(shadeInteractor.anyExpansion).thenReturn(MutableStateFlow(0f))
underTest =
KeyguardQuickAffordancesCombinedViewModel(
quickAffordanceInteractor =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = keyguardInteractor,
+ shadeInteractor = shadeInteractor,
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 58624d3..6878007 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -144,19 +144,43 @@
@Test
fun alpha() =
testScope.runTest {
- val value = collectLastValue(underTest.alpha)
- assertThat(value()).isEqualTo(0f)
+ val alpha by collectLastValue(underTest.alpha)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.OFF,
+ to = KeyguardState.LOCKSCREEN,
+ testScope = testScope,
+ )
repository.setKeyguardAlpha(0.1f)
- assertThat(value()).isEqualTo(0.1f)
+ assertThat(alpha).isEqualTo(0.1f)
repository.setKeyguardAlpha(0.5f)
- assertThat(value()).isEqualTo(0.5f)
+ assertThat(alpha).isEqualTo(0.5f)
repository.setKeyguardAlpha(0.2f)
- assertThat(value()).isEqualTo(0.2f)
+ assertThat(alpha).isEqualTo(0.2f)
repository.setKeyguardAlpha(0f)
- assertThat(value()).isEqualTo(0f)
+ assertThat(alpha).isEqualTo(0f)
occludedToLockscreenAlpha.value = 0.8f
- assertThat(value()).isEqualTo(0.8f)
+ assertThat(alpha).isEqualTo(0.8f)
+ }
+
+ @Test
+ fun alphaWhenGoneEqualsZero() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.alpha)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ testScope = testScope,
+ )
+
+ repository.setKeyguardAlpha(0.1f)
+ assertThat(alpha).isEqualTo(0f)
+ repository.setKeyguardAlpha(0.5f)
+ assertThat(alpha).isEqualTo(0f)
+ repository.setKeyguardAlpha(1f)
+ assertThat(alpha).isEqualTo(0f)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
new file mode 100644
index 0000000..d8199c5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles
+
+import android.os.Handler
+import android.service.quicksettings.Tile
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.res.R
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+/**
+ * This class tests the functionality of the RecordIssueTile. The initial state of the tile is
+ * always be inactive at the start of these tests.
+ */
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class RecordIssueTileTest : SysuiTestCase() {
+
+ @Mock private lateinit var host: QSHost
+ @Mock private lateinit var qsEventLogger: QsEventLogger
+ @Mock private lateinit var metricsLogger: MetricsLogger
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var qsLogger: QSLogger
+
+ private lateinit var tile: RecordIssueTile
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(host.context).thenReturn(mContext)
+
+ val testableLooper = TestableLooper.get(this)
+ tile =
+ RecordIssueTile(
+ host,
+ qsEventLogger,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ FalsingManagerFake(),
+ metricsLogger,
+ statusBarStateController,
+ activityStarter,
+ qsLogger
+ )
+ }
+
+ @Test
+ fun qsTileUi_shouldLookCorrect_whenInactive() {
+ tile.isRecording = false
+
+ val testState = tile.newTileState()
+ tile.handleUpdateState(testState, null)
+
+ assertThat(testState.state).isEqualTo(Tile.STATE_INACTIVE)
+ assertThat(testState.secondaryLabel.toString())
+ .isEqualTo(mContext.getString(R.string.qs_record_issue_start))
+ }
+
+ @Test
+ fun qsTileUi_shouldLookCorrect_whenRecording() {
+ tile.isRecording = true
+
+ val testState = tile.newTileState()
+ tile.handleUpdateState(testState, null)
+
+ assertThat(testState.state).isEqualTo(Tile.STATE_ACTIVE)
+ assertThat(testState.secondaryLabel.toString())
+ .isEqualTo(mContext.getString(R.string.qs_record_issue_stop))
+ }
+
+ @Test
+ fun inActiveQsTile_switchesToActive_whenClicked() {
+ tile.isRecording = false
+
+ val testState = tile.newTileState()
+ tile.handleUpdateState(testState, null)
+
+ assertThat(testState.state).isEqualTo(Tile.STATE_INACTIVE)
+ }
+
+ @Test
+ fun activeQsTile_switchesToInActive_whenClicked() {
+ tile.isRecording = true
+
+ val testState = tile.newTileState()
+ tile.handleUpdateState(testState, null)
+
+ assertThat(testState.state).isEqualTo(Tile.STATE_ACTIVE)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 39739e7..5ffbe65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -75,6 +75,7 @@
import com.android.systemui.scene.data.repository.SceneContainerRepository;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.scene.shared.logger.SceneLogger;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
@@ -138,6 +139,7 @@
@Mock private ShadeWindowLogger mShadeWindowLogger;
@Mock private SelectedUserInteractor mSelectedUserInteractor;
@Mock private UserTracker mUserTracker;
+ @Mock private SceneContainerFlags mSceneContainerFlags;
@Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
@Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener;
@@ -274,7 +276,8 @@
() -> mShadeInteractor,
mShadeWindowLogger,
() -> mSelectedUserInteractor,
- mUserTracker) {
+ mUserTracker,
+ mSceneContainerFlags) {
@Override
protected boolean isDebuggable() {
return false;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 85b9392..f0205b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -413,7 +413,7 @@
val top = 123f
val bottom = 456f
keyguardRootViewModel.onNotificationContainerBoundsChanged(top, bottom)
- assertThat(bounds).isEqualTo(NotificationContainerBounds(top, bottom))
+ assertThat(bounds).isEqualTo(NotificationContainerBounds(top = top, bottom = bottom))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
index 22dce3a..1bdf644 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
@@ -21,6 +21,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Text
+import com.android.systemui.common.shared.model.Text.Companion.loadText
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
@@ -355,14 +356,14 @@
connectivityRepository.setEthernetConnected(default = true, validated = true)
- assertThat(latest?.secondaryLabel).isNull()
- assertThat(latest?.secondaryTitle)
- .isEqualTo(ethernetIcon!!.contentDescription.toString())
+ assertThat(latest?.secondaryLabel.loadText(context))
+ .isEqualTo(ethernetIcon!!.contentDescription.loadContentDescription(context))
+ assertThat(latest?.secondaryTitle).isNull()
assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet_fully)
assertThat(latest?.icon).isNull()
assertThat(latest?.stateDescription).isNull()
assertThat(latest?.contentDescription.loadContentDescription(context))
- .isEqualTo(latest?.secondaryTitle)
+ .isEqualTo(latest?.secondaryLabel.loadText(context))
}
@Test
@@ -373,14 +374,14 @@
connectivityRepository.setEthernetConnected(default = true, validated = false)
- assertThat(latest?.secondaryLabel).isNull()
- assertThat(latest?.secondaryTitle)
- .isEqualTo(ethernetIcon!!.contentDescription.toString())
+ assertThat(latest?.secondaryLabel.loadText(context))
+ .isEqualTo(ethernetIcon!!.contentDescription.loadContentDescription(context))
+ assertThat(latest?.secondaryTitle).isNull()
assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet)
assertThat(latest?.icon).isNull()
assertThat(latest?.stateDescription).isNull()
assertThat(latest?.contentDescription.loadContentDescription(context))
- .isEqualTo(latest?.secondaryTitle)
+ .isEqualTo(latest?.secondaryLabel.loadText(context))
}
private fun setWifiNetworkWithHotspot(hotspot: WifiNetworkModel.HotspotDeviceType) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
index 01dad38..479309c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
@@ -153,6 +153,36 @@
}
@Test
+ public void testCanSkipLockScreen_updateCalledOnFacesCleared() {
+ verify(mKeyguardUpdateMonitor).registerCallback(mUpdateCallbackCaptor.capture());
+
+ // Cannot skip after there's a password/pin/pattern
+ when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+ ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+ assertThat(mKeyguardStateController.canDismissLockScreen()).isFalse();
+
+ // Unless user is authenticated
+ when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(anyInt())).thenReturn(true);
+ mUpdateCallbackCaptor.getValue().onFacesCleared();
+ assertThat(mKeyguardStateController.canDismissLockScreen()).isTrue();
+ }
+
+ @Test
+ public void testCanSkipLockScreen_updateCalledOnFingerprintssCleared() {
+ verify(mKeyguardUpdateMonitor).registerCallback(mUpdateCallbackCaptor.capture());
+
+ // Cannot skip after there's a password/pin/pattern
+ when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+ ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+ assertThat(mKeyguardStateController.canDismissLockScreen()).isFalse();
+
+ // Unless user is authenticated
+ when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(anyInt())).thenReturn(true);
+ mUpdateCallbackCaptor.getValue().onFingerprintsCleared();
+ assertThat(mKeyguardStateController.canDismissLockScreen()).isTrue();
+ }
+
+ @Test
public void testIsUnlocked() {
// Is unlocked whenever the keyguard is not showing
assertThat(mKeyguardStateController.isShowing()).isFalse();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 8585d46..5b9b390 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -119,6 +119,7 @@
import com.android.systemui.scene.data.repository.SceneContainerRepository;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.scene.shared.logger.SceneLogger;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.settings.UserTracker;
@@ -343,6 +344,8 @@
private Icon mAppBubbleIcon;
@Mock
private Display mDefaultDisplay;
+ @Mock
+ private SceneContainerFlags mSceneContainerFlags;
private final SceneTestUtils mUtils = new SceneTestUtils(this);
private final TestScope mTestScope = mUtils.getTestScope();
@@ -503,7 +506,8 @@
() -> mShadeInteractor,
mShadeWindowLogger,
() -> mSelectedUserInteractor,
- mUserTracker
+ mUserTracker,
+ mSceneContainerFlags
);
mNotificationShadeWindowController.fetchWindowRootView();
mNotificationShadeWindowController.attach();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
index 4642b47..7c5696c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -20,10 +20,10 @@
import com.android.internal.widget.LockPatternView
import com.android.internal.widget.LockscreenCredential
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
+import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
import com.android.systemui.authentication.shared.model.AuthenticationResultModel
-import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.dagger.SysUISingleton
import dagger.Binds
import dagger.Module
@@ -47,10 +47,9 @@
private val _isPatternVisible = MutableStateFlow(true)
override val isPatternVisible: StateFlow<Boolean> = _isPatternVisible.asStateFlow()
- override val throttling: MutableStateFlow<AuthenticationThrottlingModel?> =
- MutableStateFlow(null)
+ override val lockout: MutableStateFlow<AuthenticationLockoutModel?> = MutableStateFlow(null)
- override val hasThrottlingOccurred = MutableStateFlow(false)
+ override val hasLockoutOccurred = MutableStateFlow(false)
private val _isAutoConfirmFeatureEnabled = MutableStateFlow(false)
override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> =
@@ -70,10 +69,12 @@
_isPinEnhancedPrivacyEnabled.asStateFlow()
private var failedAttemptCount = 0
- private var throttlingEndTimestamp = 0L
+ private var lockoutEndTimestamp = 0L
private var credentialOverride: List<Any>? = null
private var securityMode: SecurityMode = DEFAULT_AUTHENTICATION_METHOD.toSecurityMode()
+ var lockoutStartedReportCount = 0
+
override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
return authenticationMethod.value
}
@@ -92,6 +93,10 @@
authenticationChallengeResult.emit(isSuccessful)
}
+ override suspend fun reportLockoutStarted(durationMs: Int) {
+ lockoutStartedReportCount++
+ }
+
override suspend fun getPinLength(): Int {
return (credentialOverride ?: DEFAULT_PIN).size
}
@@ -100,18 +105,18 @@
return failedAttemptCount
}
- override suspend fun getThrottlingEndTimestamp(): Long {
- return throttlingEndTimestamp
+ override suspend fun getLockoutEndTimestamp(): Long {
+ return lockoutEndTimestamp
}
fun setAutoConfirmFeatureEnabled(isEnabled: Boolean) {
_isAutoConfirmFeatureEnabled.value = isEnabled
}
- override suspend fun setThrottleDuration(durationMs: Int) {
- throttlingEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0
+ override suspend fun setLockoutDuration(durationMs: Int) {
+ lockoutEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0
if (durationMs > 0) {
- hasThrottlingOccurred.value = true
+ hasLockoutOccurred.value = true
}
}
@@ -131,18 +136,16 @@
else -> error("Unexpected credential type ${credential.type}!")
}
- return if (
- isSuccessful || failedAttemptCount < MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1
- ) {
- hasThrottlingOccurred.value = false
+ return if (isSuccessful || failedAttemptCount < MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
+ hasLockoutOccurred.value = false
AuthenticationResultModel(
isSuccessful = isSuccessful,
- throttleDurationMs = 0,
+ lockoutDurationMs = 0,
)
} else {
AuthenticationResultModel(
isSuccessful = false,
- throttleDurationMs = THROTTLE_DURATION_MS,
+ lockoutDurationMs = LOCKOUT_DURATION_MS,
)
}
}
@@ -172,9 +175,9 @@
AuthenticationPatternCoordinate(0, 1),
AuthenticationPatternCoordinate(0, 2),
)
- const val MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING = 5
- const val THROTTLE_DURATION_SECONDS = 30
- const val THROTTLE_DURATION_MS = THROTTLE_DURATION_SECONDS * 1000
+ const val MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT = 5
+ const val LOCKOUT_DURATION_SECONDS = 30
+ const val LOCKOUT_DURATION_MS = LOCKOUT_DURATION_SECONDS * 1000
const val HINTING_PIN_LENGTH = 6
val DEFAULT_PIN = buildList { repeat(HINTING_PIN_LENGTH) { add(it + 1) } }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
index e289083..a1b6587 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -31,7 +31,6 @@
@SysUISingleton
class FakeDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceAuthRepository {
-
override val isAuthenticated = MutableStateFlow(false)
override val canRunFaceAuth = MutableStateFlow(false)
private val _authenticationStatus = MutableStateFlow<FaceAuthenticationStatus?>(null)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/saver/DataSaverTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/saver/DataSaverTileKosmos.kt
new file mode 100644
index 0000000..e9a394a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/saver/DataSaverTileKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.saver
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+import com.android.systemui.statusbar.connectivity.ConnectivityModule
+
+val Kosmos.qsDataSaverTileConfig by
+ Kosmos.Fixture { ConnectivityModule.provideDataSaverTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
index 886722e..bade848 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
@@ -19,19 +19,38 @@
import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.systemui.statusbar.policy.DataSaverController.Listener;
+import java.util.ArrayList;
+import java.util.List;
+
public class FakeDataSaverController extends BaseLeakChecker<Listener> implements DataSaverController {
+ private boolean mIsEnabled = false;
+ private List<Listener> mListeners = new ArrayList<>();
+
public FakeDataSaverController(LeakCheck test) {
super(test, "datasaver");
}
@Override
public boolean isDataSaverEnabled() {
- return false;
+ return mIsEnabled;
}
@Override
public void setDataSaverEnabled(boolean enabled) {
+ mIsEnabled = enabled;
+ for (Listener listener: mListeners) {
+ listener.onDataSaverChanged(enabled);
+ }
+ }
+ @Override
+ public void addCallback(Listener listener) {
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void removeCallback(Listener listener) {
+ mListeners.remove(listener);
}
}
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index e3f1932..2ba5f6b 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -1,6 +1,8 @@
# Only classes listed here can use the Ravenwood annotations.
com.android.internal.util.ArrayUtils
+com.android.internal.os.LongArrayMultiStateCounter
+com.android.internal.os.LongArrayMultiStateCounter$LongArrayContainer
android.util.AtomicFile
android.util.DataUnit
@@ -22,6 +24,7 @@
android.util.TimeUtils
android.util.Xml
+android.os.BatteryConsumer
android.os.Binder
android.os.Binder$IdentitySupplier
android.os.FileUtils
diff --git a/services/accessibility/Android.bp b/services/accessibility/Android.bp
index e9bb763..b8cf13b 100644
--- a/services/accessibility/Android.bp
+++ b/services/accessibility/Android.bp
@@ -32,6 +32,22 @@
],
}
+java_library_static {
+ name: "AccessibilityGestureUtils",
+ srcs: [
+ "java/**/gestures/GestureMatcher.java",
+ "java/**/gestures/GestureManifold.java",
+ "java/**/gestures/MultiFingerMultiTap.java",
+ "java/**/gestures/TouchState.java",
+ ],
+ static_libs: [
+ "services.accessibility",
+ ],
+ libs: [
+ "androidx.annotation_annotation",
+ ],
+}
+
aconfig_declarations {
name: "com_android_server_accessibility_flags",
package: "com.android.server.accessibility",
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
index 903a071..e54f0c1 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
@@ -82,7 +82,7 @@
* detector. Gesture matchers are tied to a single gesture. It calls listener callback functions
* when a gesture starts or completes.
*/
-class GestureManifold implements GestureMatcher.StateChangeListener {
+public class GestureManifold implements GestureMatcher.StateChangeListener {
private static final String LOG_TAG = "GestureManifold";
@@ -111,7 +111,7 @@
// Shared state information.
private TouchState mState;
- GestureManifold(Context context, Listener listener, TouchState state, Handler handler) {
+ public GestureManifold(Context context, Listener listener, TouchState state, Handler handler) {
mContext = context;
mHandler = handler;
mListener = listener;
@@ -222,7 +222,7 @@
* @return True if the event has been appropriately handled by the gesture manifold and related
* callback functions, false if it should be handled further by the calling function.
*/
- boolean onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ public boolean onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
if (mState.isClear()) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
// Validity safeguard: if touch state is clear, then matchers should always be clear
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index 0a2a780..baae1d93 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -861,6 +861,7 @@
}
final class DetectingStateWithMultiFinger extends DetectingState {
+ private static final int TWO_FINGER_GESTURE_MAX_TAPS = 2;
// A flag set to true when two fingers have touched down.
// Used to indicate what next finger action should be.
private boolean mIsTwoFingerCountReached = false;
@@ -917,7 +918,8 @@
mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
if (event.getPointerCount() == 2) {
- if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 2, event)) {
+ if (isMultiFingerMultiTapTriggered(
+ TWO_FINGER_GESTURE_MAX_TAPS - 1, event)) {
// 3tap and hold
afterLongTapTimeoutTransitionToDraggingState(event);
} else {
@@ -962,7 +964,8 @@
// (which is a rare combo to be used aside from magnification)
if (isMultiTapTriggered(2 /* taps */) && event.getPointerCount() == 1) {
transitionToViewportDraggingStateAndClear(event);
- } else if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 2, event)
+ } else if (isMultiFingerMultiTapTriggered(
+ TWO_FINGER_GESTURE_MAX_TAPS - 1, event)
&& event.getPointerCount() == 2) {
transitionToViewportDraggingStateAndClear(event);
} else if (isActivated() && event.getPointerCount() == 2) {
@@ -1009,7 +1012,7 @@
mDisplayId, event.getX(), event.getY())) {
transitionToDelegatingStateAndClear();
- } else if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 3, event)) {
+ } else if (isMultiFingerMultiTapTriggered(TWO_FINGER_GESTURE_MAX_TAPS, event)) {
// Placing multiple fingers before a single finger, because achieving a
// multi finger multi tap also means achieving a single finger triple tap
onTripleTap(event);
@@ -1051,7 +1054,7 @@
mIsTwoFingerCountReached = false;
}
- if (mDetectTwoFingerTripleTap && mCompletedTapCount > 2) {
+ if (mDetectTwoFingerTripleTap && mCompletedTapCount > TWO_FINGER_GESTURE_MAX_TAPS - 1) {
final boolean enabled = !isActivated();
mMagnificationLogger.logMagnificationTwoFingerTripleTap(enabled);
}
@@ -1075,7 +1078,7 @@
// Only log the 3tap and hold event
if (!shortcutTriggered) {
final boolean enabled = !isActivated();
- if (mCompletedTapCount == 2) {
+ if (mCompletedTapCount == TWO_FINGER_GESTURE_MAX_TAPS - 1) {
// Two finger triple tap and hold
mMagnificationLogger.logMagnificationTwoFingerTripleTap(enabled);
} else {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
index 73c267a..75d01f5 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
@@ -481,10 +481,10 @@
if (mDetectTwoFingerTripleTap) {
mGestureMatchers.add(new MultiFingerMultiTap(context, /* fingers= */ 2,
- /* taps= */ 3, MagnificationGestureMatcher.GESTURE_TRIPLE_TAP,
+ /* taps= */ 2, MagnificationGestureMatcher.GESTURE_TRIPLE_TAP,
null));
mGestureMatchers.add(new MultiFingerMultiTapAndHold(context, /* fingers= */ 2,
- /* taps= */ 3, MagnificationGestureMatcher.GESTURE_TRIPLE_TAP_AND_HOLD,
+ /* taps= */ 2, MagnificationGestureMatcher.GESTURE_TRIPLE_TAP_AND_HOLD,
null));
}
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index c111ec3..1f89e57b 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -253,13 +253,8 @@
mInputManagerInternal.setDisplayEligibilityForPointerCapture(displayId, isEligible);
}
- void setLocalIme(int displayId) {
- // WM throws a SecurityException if the display is untrusted.
- if ((mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED)
- == Display.FLAG_TRUSTED) {
- mWindowManager.setDisplayImePolicy(displayId,
- WindowManager.DISPLAY_IME_POLICY_LOCAL);
- }
+ void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) {
+ mWindowManager.setDisplayImePolicy(displayId, policy);
}
@GuardedBy("mLock")
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 13c7924..58aa2c3 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -899,6 +899,24 @@
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) {
+ super.setDisplayImePolicy_enforcePermission();
+ synchronized (mVirtualDeviceLock) {
+ if (!mVirtualDisplays.contains(displayId)) {
+ throw new SecurityException("Display ID " + displayId
+ + " not found for this virtual device");
+ }
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mInputController.setDisplayImePolicy(displayId, policy);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@Nullable
public List<VirtualSensor> getVirtualSensorList() {
super.getVirtualSensorList_enforcePermission();
@@ -1095,7 +1113,12 @@
mInputController.setPointerAcceleration(1f, displayId);
mInputController.setDisplayEligibilityForPointerCapture(/* isEligible= */ false,
displayId);
- mInputController.setLocalIme(displayId);
+ // WM throws a SecurityException if the display is untrusted.
+ if ((mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED)
+ == Display.FLAG_TRUSTED) {
+ mInputController.setDisplayImePolicy(displayId,
+ WindowManager.DISPLAY_IME_POLICY_LOCAL);
+ }
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index b9a3c0c4..33726d1 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -17,6 +17,7 @@
package com.android.server;
import static android.os.Flags.stateOfHealthPublic;
+import static android.os.Flags.batteryServiceSupportCurrentAdbCommand;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import static com.android.server.health.Utils.copyV1Battery;
@@ -927,9 +928,12 @@
pw.println("Battery service (battery) commands:");
pw.println(" help");
pw.println(" Print this help text.");
- pw.println(" get [-f] [ac|usb|wireless|dock|status|level|temp|present|counter|invalid]");
- pw.println(" set [-f] "
- + "[ac|usb|wireless|dock|status|level|temp|present|counter|invalid] <value>");
+ String getSetOptions = "ac|usb|wireless|dock|status|level|temp|present|counter|invalid";
+ if (batteryServiceSupportCurrentAdbCommand()) {
+ getSetOptions += "|current_now|current_average";
+ }
+ pw.println(" get [-f] [" + getSetOptions + "]");
+ pw.println(" set [-f] [" + getSetOptions + "] <value>");
pw.println(" Force a battery property value, freezing battery state.");
pw.println(" -f: force a battery change broadcast be sent, prints new sequence.");
pw.println(" unplug [-f]");
@@ -1001,6 +1005,16 @@
case "counter":
pw.println(mHealthInfo.batteryChargeCounterUah);
break;
+ case "current_now":
+ if (batteryServiceSupportCurrentAdbCommand()) {
+ pw.println(mHealthInfo.batteryCurrentMicroamps);
+ }
+ break;
+ case "current_average":
+ if (batteryServiceSupportCurrentAdbCommand()) {
+ pw.println(mHealthInfo.batteryCurrentAverageMicroamps);
+ }
+ break;
case "temp":
pw.println(mHealthInfo.batteryTemperatureTenthsCelsius);
break;
@@ -1058,6 +1072,16 @@
case "counter":
mHealthInfo.batteryChargeCounterUah = Integer.parseInt(value);
break;
+ case "current_now":
+ if (batteryServiceSupportCurrentAdbCommand()) {
+ mHealthInfo.batteryCurrentMicroamps = Integer.parseInt(value);
+ }
+ break;
+ case "current_average":
+ if (batteryServiceSupportCurrentAdbCommand()) {
+ mHealthInfo.batteryCurrentAverageMicroamps =
+ Integer.parseInt(value);
+ }
case "temp":
mHealthInfo.batteryTemperatureTenthsCelsius = Integer.parseInt(value);
break;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3e533a6..ac173f3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -11972,6 +11972,7 @@
boolean dumpSwapPss;
boolean dumpProto;
boolean mDumpPrivateDirty;
+ boolean mDumpAllocatorStats;
}
@NeverCompile // Avoid size overhead of debugging code.
@@ -11991,6 +11992,7 @@
opts.dumpSwapPss = false;
opts.dumpProto = asProto;
opts.mDumpPrivateDirty = false;
+ opts.mDumpAllocatorStats = false;
int opti = 0;
while (opti < args.length) {
@@ -12027,7 +12029,8 @@
opts.isCheckinRequest = true;
} else if ("--proto".equals(opt)) {
opts.dumpProto = true;
-
+ } else if ("--logstats".equals(opt)) {
+ opts.mDumpAllocatorStats = true;
} else if ("-h".equals(opt)) {
pw.println("meminfo dump options: [-a] [-d] [-c] [-s] [--oom] [process]");
pw.println(" -a: include all available information for each process.");
@@ -12238,7 +12241,8 @@
try {
thread.dumpMemInfo(tp.getWriteFd(),
mi, opts.isCheckinRequest, opts.dumpFullDetails,
- opts.dumpDalvik, opts.dumpSummaryOnly, opts.dumpUnreachable, innerArgs);
+ opts.dumpDalvik, opts.dumpSummaryOnly, opts.dumpUnreachable,
+ opts.mDumpAllocatorStats, innerArgs);
tp.go(fd, opts.dumpUnreachable ? 30000 : 5000);
} finally {
tp.kill();
@@ -17510,6 +17514,8 @@
* other {@code ActivityManager#USER_OP_*} codes for failure.
*
*/
+ // TODO(b/302662311): Add javadoc changes corresponding to the user property that allows
+ // delayed locking behavior once the private space flag is finalized.
@Override
public int stopUserWithDelayedLocking(final int userId, boolean force,
final IStopUserCallback callback) {
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index caafb42..fc8ad6bc 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -35,6 +35,7 @@
import android.app.ForegroundServiceDelegationOptions;
import android.content.ComponentName;
import android.content.pm.ServiceInfo;
+import android.os.Trace;
import android.util.ArrayMap;
import android.util.IntArray;
import android.util.LongArray;
@@ -134,6 +135,11 @@
* call of the right type will also be associated and logged
*/
public void logForegroundServiceStart(int uid, int pid, ServiceRecord record) {
+ if (record.getComponentName() != null) {
+ final String traceTag = record.getComponentName().flattenToString() + ":" + uid;
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, traceTag,
+ "foregroundService", record.foregroundServiceType);
+ }
// initialize the UID stack
UidState uidState = mUids.get(uid);
if (uidState == null) {
@@ -205,6 +211,11 @@
// we need to log all the API end events and remove the start events
// then we remove the FGS from the various stacks
// and also clean up the start calls stack by UID
+ if (record.getComponentName() != null) {
+ final String traceTag = record.getComponentName().flattenToString() + ":" + uid;
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ traceTag, record.hashCode());
+ }
final IntArray apiTypes = convertFgsTypeToApiTypes(record.foregroundServiceType);
final UidState uidState = mUids.get(uid);
if (apiTypes.size() == 0) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 47a99fe..a6b532c 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -356,6 +356,8 @@
* Once total number of unlocked users reach mMaxRunningUsers, least recently used user
* will be locked.
*/
+ // TODO(b/302662311): Add javadoc changes corresponding to the user property that allows
+ // delayed locking behavior once the private space flag is finalized.
@GuardedBy("mLock")
private boolean mDelayUserDataLocking;
@@ -365,11 +367,12 @@
private volatile boolean mAllowUserUnlocking;
/**
- * Keep track of last active users for mDelayUserDataLocking.
- * The latest stopped user is placed in front while the least recently stopped user in back.
+ * Keep track of last active users for delayUserDataLocking.
+ * The most recently stopped user with delayed locking is placed in front, while the least
+ * recently stopped user in back.
*/
@GuardedBy("mLock")
- private final ArrayList<Integer> mLastActiveUsers = new ArrayList<>();
+ private final ArrayList<Integer> mLastActiveUsersForDelayedLocking = new ArrayList<>();
/**
* Map of userId to {@link UserCompletedEventType} event flags, indicating which as-yet-
@@ -1011,20 +1014,21 @@
Slogf.i(TAG, "stopSingleUserLU userId=" + userId);
final UserState uss = mStartedUsers.get(userId);
if (uss == null) { // User is not started
- // If mDelayUserDataLocking is set and allowDelayedLocking is not set, we need to lock
- // the requested user as the client wants to stop and lock the user. On the other hand,
- // having keyEvictedCallback set will lead into locking user if mDelayUserDataLocking
- // is set as that means client wants to lock the user immediately.
- // If mDelayUserDataLocking is not set, the user was already locked when it was stopped
- // and no further action is necessary.
- if (mDelayUserDataLocking) {
+ // If canDelayDataLockingForUser() is true and allowDelayedLocking is false, we need
+ // to lock the requested user as the client wants to stop and lock the user. On the
+ // other hand, having keyEvictedCallback set will lead into locking user if
+ // canDelayDataLockingForUser() is true as that means client wants to lock the user
+ // immediately.
+ // If canDelayDataLockingForUser() is false, the user was already locked when it was
+ // stopped and no further action is necessary.
+ if (canDelayDataLockingForUser(userId)) {
if (allowDelayedLocking && keyEvictedCallback != null) {
Slogf.wtf(TAG, "allowDelayedLocking set with KeyEvictedCallback, ignore it"
+ " and lock user:" + userId, new RuntimeException());
allowDelayedLocking = false;
}
if (!allowDelayedLocking) {
- if (mLastActiveUsers.remove(Integer.valueOf(userId))) {
+ if (mLastActiveUsersForDelayedLocking.remove(Integer.valueOf(userId))) {
// should lock the user, user is already gone
final ArrayList<KeyEvictedCallback> keyEvictedCallbacks;
if (keyEvictedCallback != null) {
@@ -1354,14 +1358,21 @@
@GuardedBy("mLock")
private int updateUserToLockLU(@UserIdInt int userId, boolean allowDelayedLocking) {
int userIdToLock = userId;
- if (mDelayUserDataLocking && allowDelayedLocking && !getUserInfo(userId).isEphemeral()
+ // TODO: Decouple the delayed locking flows from mMaxRunningUsers or rename the property to
+ // state maximum running unlocked users specifically
+ if (canDelayDataLockingForUser(userIdToLock) && allowDelayedLocking
+ && !getUserInfo(userId).isEphemeral()
&& !hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, userId)) {
- mLastActiveUsers.remove((Integer) userId); // arg should be object, not index
- mLastActiveUsers.add(0, userId);
- int totalUnlockedUsers = mStartedUsers.size() + mLastActiveUsers.size();
+ // arg should be object, not index
+ mLastActiveUsersForDelayedLocking.remove((Integer) userId);
+ mLastActiveUsersForDelayedLocking.add(0, userId);
+ int totalUnlockedUsers = mStartedUsers.size()
+ + mLastActiveUsersForDelayedLocking.size();
if (totalUnlockedUsers > mMaxRunningUsers) { // should lock a user
- userIdToLock = mLastActiveUsers.get(mLastActiveUsers.size() - 1);
- mLastActiveUsers.remove(mLastActiveUsers.size() - 1);
+ userIdToLock = mLastActiveUsersForDelayedLocking.get(
+ mLastActiveUsersForDelayedLocking.size() - 1);
+ mLastActiveUsersForDelayedLocking
+ .remove(mLastActiveUsersForDelayedLocking.size() - 1);
Slogf.i(TAG, "finishUserStopped, stopping user:" + userId
+ " lock user:" + userIdToLock);
} else {
@@ -1374,6 +1385,24 @@
}
/**
+ * Returns whether the user can have its CE storage left unlocked, even when it is stopped,
+ * either due to a global device configuration or an individual user's property.
+ */
+ private boolean canDelayDataLockingForUser(@UserIdInt int userIdToLock) {
+ if (allowBiometricUnlockForPrivateProfile()) {
+ final UserProperties userProperties = getUserProperties(userIdToLock);
+ return (mDelayUserDataLocking || (userProperties != null
+ && userProperties.getAllowStoppingUserWithDelayedLocking()));
+ }
+ return mDelayUserDataLocking;
+ }
+
+ private boolean allowBiometricUnlockForPrivateProfile() {
+ return android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace();
+ }
+
+ /**
* Determines the list of users that should be stopped together with the specified
* {@code userId}. The returned list includes {@code userId}.
*/
@@ -3161,7 +3190,7 @@
pw.println(" mCurrentProfileIds:" + Arrays.toString(mCurrentProfileIds));
pw.println(" mCurrentUserId:" + mCurrentUserId);
pw.println(" mTargetUserId:" + mTargetUserId);
- pw.println(" mLastActiveUsers:" + mLastActiveUsers);
+ pw.println(" mLastActiveUsersForDelayedLocking:" + mLastActiveUsersForDelayedLocking);
pw.println(" mDelayUserDataLocking:" + mDelayUserDataLocking);
pw.println(" mAllowUserUnlocking:" + mAllowUserUnlocking);
pw.println(" shouldStopUserOnSwitch():" + shouldStopUserOnSwitch());
diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java
index ffdab7d..9ae43a0 100644
--- a/services/core/java/com/android/server/audio/AdiDeviceState.java
+++ b/services/core/java/com/android/server/audio/AdiDeviceState.java
@@ -235,9 +235,10 @@
* {@link AdiDeviceState#toPersistableString()}.
*/
public static int getPeristedMaxSize() {
- return 36; /* (mDeviceType)2 + (mDeviceAddress)17 + (mInternalDeviceType)9 + (mSAEnabled)1
+ return 39; /* (mDeviceType)2 + (mDeviceAddress)17 + (mInternalDeviceType)9 + (mSAEnabled)1
+ (mHasHeadTracker)1 + (mHasHeadTrackerEnabled)1
- + (SETTINGS_FIELD_SEPARATOR)5 */
+ + (mAudioDeviceCategory)1 + (SETTINGS_FIELD_SEPARATOR)6
+ + (SETTING_DEVICE_SEPARATOR)1 */
}
@Nullable
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 9cfcb16..8091753 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -2037,9 +2037,8 @@
} break;
case MSG_L_UPDATED_ADI_DEVICE_STATE:
- synchronized (mDeviceStateLock) {
- mAudioService.onUpdatedAdiDeviceState((AdiDeviceState) msg.obj);
- } break;
+ mAudioService.onUpdatedAdiDeviceState((AdiDeviceState) msg.obj);
+ break;
default:
Log.wtf(TAG, "Invalid message " + msg.what);
@@ -2687,11 +2686,15 @@
return;
}
final SettingsAdapter settingsAdapter = mAudioService.getSettings();
- boolean res = settingsAdapter.putSecureStringForUser(mAudioService.getContentResolver(),
- Settings.Secure.AUDIO_DEVICE_INVENTORY,
- deviceSettings, UserHandle.USER_CURRENT);
- if (!res) {
- Log.e(TAG, "error saving AdiDeviceState: " + deviceSettings);
+ try {
+ boolean res = settingsAdapter.putSecureStringForUser(mAudioService.getContentResolver(),
+ Settings.Secure.AUDIO_DEVICE_INVENTORY,
+ deviceSettings, UserHandle.USER_CURRENT);
+ if (!res) {
+ Log.e(TAG, "error saving AdiDeviceState: " + deviceSettings);
+ }
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "error saving AdiDeviceState: " + deviceSettings, e);
}
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index d077ebc..34cfdfa 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -30,7 +30,6 @@
import static android.media.AudioSystem.isBluetoothScoOutDevice;
import static android.media.audio.Flags.automaticBtDeviceType;
-
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import android.annotation.NonNull;
@@ -81,7 +80,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
@@ -104,6 +102,14 @@
private static final String SETTING_DEVICE_SEPARATOR_CHAR = "|";
private static final String SETTING_DEVICE_SEPARATOR = "\\|";
+ /** Max String length that can be persisted within the Settings. */
+ // LINT.IfChange(settings_max_length_per_string)
+ private static final int MAX_SETTINGS_LENGTH_PER_STRING = 32768;
+ // LINT.ThenChange(/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java)
+
+ private static final int MAX_DEVICE_INVENTORY_ENTRIES =
+ MAX_SETTINGS_LENGTH_PER_STRING / AdiDeviceState.getPeristedMaxSize();
+
// lock to synchronize all access to mConnectedDevices and mApmConnectedDevices
private final Object mDevicesLock = new Object();
@@ -113,7 +119,8 @@
private final Object mDeviceInventoryLock = new Object();
@GuardedBy("mDeviceInventoryLock")
- private final HashMap<Pair<Integer, String>, AdiDeviceState> mDeviceInventory = new HashMap<>();
+ private final LinkedHashMap<Pair<Integer, String>, AdiDeviceState> mDeviceInventory =
+ new LinkedHashMap<>();
Collection<AdiDeviceState> getImmutableDeviceInventory() {
final List<AdiDeviceState> newList;
@@ -136,6 +143,7 @@
oldState.setSAEnabled(newState.isSAEnabled());
return oldState;
});
+ checkDeviceInventorySize_l();
}
mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState);
}
@@ -173,6 +181,8 @@
ads.setAudioDeviceCategory(category);
mDeviceInventory.put(ads.getDeviceId(), ads);
+ checkDeviceInventorySize_l();
+
mDeviceBroker.postUpdatedAdiDeviceState(ads);
mDeviceBroker.postPersistAudioDeviceSettings();
}
@@ -200,6 +210,7 @@
}
return oldState;
});
+ checkDeviceInventorySize_l();
}
if (updatedCategory.get()) {
mDeviceBroker.postUpdatedAdiDeviceState(deviceState);
@@ -272,6 +283,18 @@
}
}
+ @GuardedBy("mDeviceInventoryLock")
+ private void checkDeviceInventorySize_l() {
+ if (mDeviceInventory.size() > MAX_DEVICE_INVENTORY_ENTRIES) {
+ // remove the first element
+ Iterator<Entry<Pair<Integer, String>, AdiDeviceState>> iterator =
+ mDeviceInventory.entrySet().iterator();
+ if (iterator.hasNext()) {
+ iterator.remove();
+ }
+ }
+ }
+
@GuardedBy({"mDevicesLock", "mDeviceInventoryLock"})
private boolean synchronizeBleDeviceInInventory(AdiDeviceState updatedDevice) {
for (DeviceInfo di : mConnectedDevices.values()) {
@@ -2806,7 +2829,7 @@
deviceCatalogSize = mDeviceInventory.size();
final StringBuilder settingsBuilder = new StringBuilder(
- deviceCatalogSize * AdiDeviceState.getPeristedMaxSize());
+ deviceCatalogSize * AdiDeviceState.getPeristedMaxSize());
Iterator<AdiDeviceState> iterator = mDeviceInventory.values().iterator();
if (iterator.hasNext()) {
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 3b7d80c..1e5e147 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -1292,7 +1292,11 @@
*/
private boolean isPackageStopped(String packageName, int userId) {
if (android.content.pm.Flags.stayStopped()) {
- return mPackageManagerInternal.isPackageStopped(packageName, userId);
+ try {
+ return mPackageManagerInternal.isPackageStopped(packageName, userId);
+ } catch (IllegalArgumentException e) {
+ Log.d(TAG, "Couldn't determine stopped state for unknown package: " + packageName);
+ }
}
return false;
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 3529b04..b1b1dba 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -122,11 +122,10 @@
/**
* Flag: This flag identifies secondary displays that should show system decorations, such as
- * status bar, navigation bar, home activity or IME.
+ * navigation bar, home activity or wallpaper.
* <p>Note that this flag doesn't work without {@link #FLAG_TRUSTED}</p>
* @hide
*/
- // TODO (b/114338689): Remove the flag and use IWindowManager#setShouldShowSystemDecors
public static final int FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 12;
/**
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index dcb86a7..66807ae 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -23,7 +23,9 @@
import android.annotation.RequiresPermission;
import android.annotation.UiThread;
import android.content.ComponentName;
+import android.content.Context;
import android.content.pm.PackageManagerInternal;
+import android.hardware.input.InputManager;
import android.hardware.input.InputManagerGlobal;
import android.os.Handler;
import android.os.IBinder;
@@ -64,6 +66,7 @@
private static final int LONG_EVENT_BUFFER_SIZE = EVENT_BUFFER_SIZE * 20;
private static final long HANDWRITING_DELEGATION_IDLE_TIMEOUT_MS = 3000;
+ private final Context mContext;
// This must be the looper for the UiThread.
private final Looper mLooper;
private final InputManagerInternal mInputManagerInternal;
@@ -87,7 +90,9 @@
private int mCurrentRequestId;
@AnyThread
- HandwritingModeController(Looper uiThreadLooper, Runnable inkWindowInitRunnable) {
+ HandwritingModeController(Context context, Looper uiThreadLooper,
+ Runnable inkWindowInitRunnable) {
+ mContext = context;
mLooper = uiThreadLooper;
mCurrentDisplayId = Display.INVALID_DISPLAY;
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
@@ -285,7 +290,14 @@
mHandwritingSurface.startIntercepting(imePid, imeUid);
// Unset the pointer icon for the stylus in case the app had set it.
- InputManagerGlobal.getInstance().setPointerIconType(PointerIcon.TYPE_NOT_SPECIFIED);
+ if (com.android.input.flags.Flags.enablePointerChoreographer()) {
+ Objects.requireNonNull(mContext.getSystemService(InputManager.class)).setPointerIcon(
+ PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED),
+ downEvent.getDisplayId(), downEvent.getDeviceId(), downEvent.getPointerId(0),
+ mHandwritingSurface.getInputChannel().getToken());
+ } else {
+ InputManagerGlobal.getInstance().setPointerIconType(PointerIcon.TYPE_NOT_SPECIFIED);
+ }
return new HandwritingSession(mCurrentRequestId, mHandwritingSurface.getInputChannel(),
mHandwritingBuffer);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 09c388f..30e9f5b 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1713,7 +1713,7 @@
com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
mNonPreemptibleInputMethods = mRes.getStringArray(
com.android.internal.R.array.config_nonPreemptibleInputMethods);
- mHwController = new HandwritingModeController(thread.getLooper(),
+ mHwController = new HandwritingModeController(mContext, thread.getLooper(),
new InkWindowInitializer());
registerDeviceListenerAndCheckStylusSupport();
}
@@ -5275,7 +5275,7 @@
return true;
}
}
- mSettings.appendAndPutEnabledInputMethodLocked(id, false);
+ mSettings.appendAndPutEnabledInputMethodLocked(id);
// Previous state was disabled.
return false;
} else {
@@ -5612,7 +5612,7 @@
if (enabled) {
if (!settings.getEnabledInputMethodListLocked().contains(
methodMap.get(imeId))) {
- settings.appendAndPutEnabledInputMethodLocked(imeId, false);
+ settings.appendAndPutEnabledInputMethodLocked(imeId);
}
} else {
settings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
@@ -5626,7 +5626,7 @@
@Override
public void setVirtualDeviceInputMethodForAllUsers(int deviceId, @Nullable String imeId) {
// TODO(b/287269288): validate that id belongs to a valid virtual device instead.
- Preconditions.checkArgument(deviceId == Context.DEVICE_ID_DEFAULT,
+ Preconditions.checkArgument(deviceId != Context.DEVICE_ID_DEFAULT,
"DeviceId " + deviceId + " does not belong to a virtual device.");
synchronized (ImfLock.class) {
if (imeId == null) {
@@ -6357,7 +6357,7 @@
}
}
if (!previouslyEnabled) {
- settings.appendAndPutEnabledInputMethodLocked(imeId, false);
+ settings.appendAndPutEnabledInputMethodLocked(imeId);
}
}
} else {
@@ -6501,7 +6501,7 @@
settings.putEnabledInputMethodsStr("");
nextEnabledImes.forEach(
imi -> settings.appendAndPutEnabledInputMethodLocked(
- imi.getId(), false));
+ imi.getId()));
// Reset selected IME.
settings.putSelectedInputMethod(nextIme);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index acf9a7f..2128356 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -362,10 +362,7 @@
return result;
}
- void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
- if (reloadInputMethodStr) {
- getEnabledInputMethodsStr();
- }
+ void appendAndPutEnabledInputMethodLocked(String id) {
if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
// Add in the newly enabled input method.
putEnabledInputMethodsStr(id);
diff --git a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
index a00999d..7cf3983 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
@@ -39,6 +39,7 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.media.flags.Flags;
import java.util.ArrayList;
import java.util.HashMap;
@@ -219,7 +220,10 @@
BluetoothRouteInfo
newBtRoute = new BluetoothRouteInfo();
newBtRoute.mBtDevice = device;
- String deviceName = device.getName();
+ String deviceName =
+ Flags.enableUseOfBluetoothDeviceGetAliasForMr2infoGetName()
+ ? device.getAlias()
+ : device.getName();
if (TextUtils.isEmpty(deviceName)) {
deviceName = mContext.getResources().getText(R.string.unknownName).toString();
}
diff --git a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
index 041fceaf..ede2d27 100644
--- a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
@@ -43,6 +43,7 @@
import android.util.SparseIntArray;
import com.android.internal.R;
+import com.android.media.flags.Flags;
import java.util.ArrayList;
import java.util.HashMap;
@@ -283,7 +284,10 @@
newBtRoute.mBtDevice = device;
String routeId = device.getAddress();
- String deviceName = device.getName();
+ String deviceName =
+ Flags.enableUseOfBluetoothDeviceGetAliasForMr2infoGetName()
+ ? device.getAlias()
+ : device.getName();
if (TextUtils.isEmpty(deviceName)) {
deviceName = mContext.getResources().getText(R.string.unknownName).toString();
}
diff --git a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
index 9fdeda4..8855666 100644
--- a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
+++ b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
@@ -16,14 +16,23 @@
package com.android.server.notification;
+import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
+
import android.app.UiModeManager;
import android.app.WallpaperManager;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.hardware.display.ColorDisplayManager;
import android.os.Binder;
import android.os.PowerManager;
import android.service.notification.DeviceEffectsApplier;
import android.service.notification.ZenDeviceEffects;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
+
+import com.android.internal.annotations.GuardedBy;
/** Default implementation for {@link DeviceEffectsApplier}. */
class DefaultDeviceEffectsApplier implements DeviceEffectsApplier {
@@ -34,13 +43,24 @@
private static final int SATURATION_LEVEL_FULL_COLOR = 100;
private static final float WALLPAPER_DIM_AMOUNT_DIMMED = 0.6f;
private static final float WALLPAPER_DIM_AMOUNT_NORMAL = 0f;
+ private static final IntentFilter SCREEN_OFF_INTENT_FILTER = new IntentFilter(
+ Intent.ACTION_SCREEN_OFF);
+ private final Context mContext;
private final ColorDisplayManager mColorDisplayManager;
private final PowerManager mPowerManager;
private final UiModeManager mUiModeManager;
private final WallpaperManager mWallpaperManager;
+ private final Object mRegisterReceiverLock = new Object();
+ @GuardedBy("mRegisterReceiverLock")
+ private boolean mIsScreenOffReceiverRegistered;
+
+ private ZenDeviceEffects mLastAppliedEffects = new ZenDeviceEffects.Builder().build();
+ private boolean mPendingNightMode;
+
DefaultDeviceEffectsApplier(Context context) {
+ mContext = context;
mColorDisplayManager = context.getSystemService(ColorDisplayManager.class);
mPowerManager = context.getSystemService(PowerManager.class);
mUiModeManager = context.getSystemService(UiModeManager.class);
@@ -48,24 +68,90 @@
}
@Override
- public void apply(ZenDeviceEffects effects) {
+ public void apply(ZenDeviceEffects effects, @ConfigChangeOrigin int origin) {
Binder.withCleanCallingIdentity(() -> {
- mPowerManager.suppressAmbientDisplay(SUPPRESS_AMBIENT_DISPLAY_TOKEN,
- effects.shouldSuppressAmbientDisplay());
-
- if (mColorDisplayManager != null) {
- mColorDisplayManager.setSaturationLevel(
- effects.shouldDisplayGrayscale() ? SATURATION_LEVEL_GRAYSCALE
- : SATURATION_LEVEL_FULL_COLOR);
+ if (mLastAppliedEffects.shouldSuppressAmbientDisplay()
+ != effects.shouldSuppressAmbientDisplay()) {
+ mPowerManager.suppressAmbientDisplay(SUPPRESS_AMBIENT_DISPLAY_TOKEN,
+ effects.shouldSuppressAmbientDisplay());
}
- if (mWallpaperManager != null) {
- mWallpaperManager.setWallpaperDimAmount(
- effects.shouldDimWallpaper() ? WALLPAPER_DIM_AMOUNT_DIMMED
- : WALLPAPER_DIM_AMOUNT_NORMAL);
+ if (mLastAppliedEffects.shouldDisplayGrayscale() != effects.shouldDisplayGrayscale()) {
+ if (mColorDisplayManager != null) {
+ mColorDisplayManager.setSaturationLevel(
+ effects.shouldDisplayGrayscale() ? SATURATION_LEVEL_GRAYSCALE
+ : SATURATION_LEVEL_FULL_COLOR);
+ }
}
- // TODO: b/308673343 - Apply dark theme (via UiModeManager) when screen is off.
+ if (mLastAppliedEffects.shouldDimWallpaper() != effects.shouldDimWallpaper()) {
+ if (mWallpaperManager != null) {
+ mWallpaperManager.setWallpaperDimAmount(
+ effects.shouldDimWallpaper() ? WALLPAPER_DIM_AMOUNT_DIMMED
+ : WALLPAPER_DIM_AMOUNT_NORMAL);
+ }
+ }
+
+ if (mLastAppliedEffects.shouldUseNightMode() != effects.shouldUseNightMode()) {
+ updateOrScheduleNightMode(effects.shouldUseNightMode(), origin);
+ }
});
+
+ mLastAppliedEffects = effects;
+ }
+
+ private void updateOrScheduleNightMode(boolean useNightMode, @ConfigChangeOrigin int origin) {
+ mPendingNightMode = useNightMode;
+
+ // Changing the theme can be disruptive for the user (Activities are likely recreated, may
+ // lose some state). Therefore we only apply the change immediately if the rule was
+ // activated manually, or we are initializing, or the screen is currently off/dreaming.
+ if (origin == ZenModeConfig.UPDATE_ORIGIN_INIT
+ || origin == ZenModeConfig.UPDATE_ORIGIN_INIT_USER
+ || origin == ZenModeConfig.UPDATE_ORIGIN_USER
+ || origin == ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+ || !mPowerManager.isInteractive()) {
+ unregisterScreenOffReceiver();
+ updateNightModeImmediately(useNightMode);
+ } else {
+ registerScreenOffReceiver();
+ }
+ }
+
+ @GuardedBy("mRegisterReceiverLock")
+ private final BroadcastReceiver mNightModeWhenScreenOff = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ unregisterScreenOffReceiver();
+ updateNightModeImmediately(mPendingNightMode);
+ }
+ };
+
+ private void updateNightModeImmediately(boolean useNightMode) {
+ Binder.withCleanCallingIdentity(() -> {
+ // TODO: b/314285749 - Placeholder; use real APIs when available.
+ mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
+ mUiModeManager.setNightModeActivatedForCustomMode(MODE_NIGHT_CUSTOM_TYPE_BEDTIME,
+ useNightMode);
+ });
+ }
+
+ private void registerScreenOffReceiver() {
+ synchronized (mRegisterReceiverLock) {
+ if (!mIsScreenOffReceiverRegistered) {
+ mContext.registerReceiver(mNightModeWhenScreenOff, SCREEN_OFF_INTENT_FILTER,
+ Context.RECEIVER_NOT_EXPORTED);
+ mIsScreenOffReceiverRegistered = true;
+ }
+ }
+ }
+
+ private void unregisterScreenOffReceiver() {
+ synchronized (mRegisterReceiverLock) {
+ if (mIsScreenOffReceiverRegistered) {
+ mIsScreenOffReceiverRegistered = false;
+ mContext.unregisterReceiver(mNightModeWhenScreenOff);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 02845fb..c2a145d 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5299,11 +5299,11 @@
public void setZenMode(int mode, Uri conditionId, String reason) throws RemoteException {
enforceSystemOrSystemUI("INotificationManager.setZenMode");
final int callingUid = Binder.getCallingUid();
- final boolean isSystemOrSystemUi = isCallerSystemOrSystemUi();
final long identity = Binder.clearCallingIdentity();
try {
- mZenModeHelper.setManualZenMode(mode, conditionId, null, reason, callingUid,
- isSystemOrSystemUi);
+ mZenModeHelper.setManualZenMode(mode, conditionId,
+ ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, // Checked by enforce()
+ reason, /* caller= */ null, callingUid);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -5360,10 +5360,11 @@
}
return mZenModeHelper.addAutomaticZenRule(rulePkg, automaticZenRule,
- "addAutomaticZenRule", Binder.getCallingUid(),
- // TODO: b/308670715: Distinguish FROM_APP from FROM_USER
- isCallerSystemOrSystemUi() ? ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI
- : ZenModeHelper.FROM_APP);
+ // TODO: b/308670715: Distinguish origin properly (e.g. USER if creating a rule
+ // manually in Settings).
+ isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+ : ZenModeConfig.UPDATE_ORIGIN_APP,
+ "addAutomaticZenRule", Binder.getCallingUid());
}
@Override
@@ -5379,11 +5380,12 @@
Objects.requireNonNull(automaticZenRule.getConditionId(), "ConditionId is null");
enforcePolicyAccess(Binder.getCallingUid(), "updateAutomaticZenRule");
+ // TODO: b/308670715: Distinguish origin properly (e.g. USER if updating a rule
+ // manually in Settings).
return mZenModeHelper.updateAutomaticZenRule(id, automaticZenRule,
- "updateAutomaticZenRule", Binder.getCallingUid(),
- // TODO: b/308670715: Distinguish FROM_APP from FROM_USER
- isCallerSystemOrSystemUi() ? ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI
- : ZenModeHelper.FROM_APP);
+ isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+ : ZenModeConfig.UPDATE_ORIGIN_APP,
+ "updateAutomaticZenRule", Binder.getCallingUid());
}
@Override
@@ -5392,8 +5394,12 @@
// Verify that they can modify zen rules.
enforcePolicyAccess(Binder.getCallingUid(), "removeAutomaticZenRule");
- return mZenModeHelper.removeAutomaticZenRule(id, "removeAutomaticZenRule",
- Binder.getCallingUid(), isCallerSystemOrSystemUi());
+ // TODO: b/308670715: Distinguish origin properly (e.g. USER if removing a rule
+ // manually in Settings).
+ return mZenModeHelper.removeAutomaticZenRule(id,
+ isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+ : ZenModeConfig.UPDATE_ORIGIN_APP,
+ "removeAutomaticZenRule", Binder.getCallingUid());
}
@Override
@@ -5402,8 +5408,9 @@
enforceSystemOrSystemUI("removeAutomaticZenRules");
return mZenModeHelper.removeAutomaticZenRules(packageName,
- packageName + "|removeAutomaticZenRules", Binder.getCallingUid(),
- isCallerSystemOrSystemUi());
+ isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+ : ZenModeConfig.UPDATE_ORIGIN_APP,
+ packageName + "|removeAutomaticZenRules", Binder.getCallingUid());
}
@Override
@@ -5421,8 +5428,12 @@
enforcePolicyAccess(Binder.getCallingUid(), "setAutomaticZenRuleState");
- mZenModeHelper.setAutomaticZenRuleState(id, condition, Binder.getCallingUid(),
- isCallerSystemOrSystemUi());
+ // TODO: b/308670715: Distinguish origin properly (e.g. USER if toggling a rule
+ // manually in Settings).
+ mZenModeHelper.setAutomaticZenRuleState(id, condition,
+ isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+ : ZenModeConfig.UPDATE_ORIGIN_APP,
+ Binder.getCallingUid());
}
@Override
@@ -5440,8 +5451,11 @@
final long identity = Binder.clearCallingIdentity();
try {
- mZenModeHelper.setManualZenMode(zen, null, pkg, "setInterruptionFilter",
- callingUid, isSystemOrSystemUi);
+ mZenModeHelper.setManualZenMode(zen, null,
+ isSystemOrSystemUi ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+ : ZenModeConfig.UPDATE_ORIGIN_APP,
+ /* reason= */ "setInterruptionFilter", /* caller= */ pkg,
+ callingUid);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -5806,7 +5820,10 @@
} else {
ZenLog.traceSetNotificationPolicy(pkg, applicationInfo.targetSdkVersion,
policy);
- mZenModeHelper.setNotificationPolicy(policy, callingUid, isSystemOrSystemUi);
+ mZenModeHelper.setNotificationPolicy(policy,
+ isSystemOrSystemUi ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+ : ZenModeConfig.UPDATE_ORIGIN_APP,
+ callingUid);
}
} catch (RemoteException e) {
Slog.e(TAG, "Failed to set notification policy", e);
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 6a7eebb..fd86870 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -59,6 +59,7 @@
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.NotificationListenerService;
import android.service.notification.RankingHelperProto;
+import android.service.notification.ZenModeConfig;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArrayMap;
@@ -1862,12 +1863,16 @@
public void updateZenPolicy(boolean areChannelsBypassingDnd, int callingUid,
boolean fromSystemOrSystemUi) {
NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
- mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
- policy.priorityCategories, policy.priorityCallSenders,
- policy.priorityMessageSenders, policy.suppressedVisualEffects,
- (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND
- : 0),
- policy.priorityConversationSenders), callingUid, fromSystemOrSystemUi);
+ mZenModeHelper.setNotificationPolicy(
+ new NotificationManager.Policy(
+ policy.priorityCategories, policy.priorityCallSenders,
+ policy.priorityMessageSenders, policy.suppressedVisualEffects,
+ (areChannelsBypassingDnd
+ ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND : 0),
+ policy.priorityConversationSenders),
+ fromSystemOrSystemUi ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+ : ZenModeConfig.UPDATE_ORIGIN_APP,
+ callingUid);
}
// TODO: b/310620812 - rename to hasPriorityChannels() when modes_api is inlined.
diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java
index 6ecd799..86aa2d8 100644
--- a/services/core/java/com/android/server/notification/ZenModeConditions.java
+++ b/services/core/java/com/android/server/notification/ZenModeConditions.java
@@ -111,8 +111,10 @@
public void onServiceAdded(ComponentName component) {
if (DEBUG) Log.d(TAG, "onServiceAdded " + component);
final int callingUid = Binder.getCallingUid();
- mHelper.setConfig(mHelper.getConfig(), component, "zmc.onServiceAdded:" + component,
- callingUid, callingUid == Process.SYSTEM_UID);
+ mHelper.setConfig(mHelper.getConfig(), component,
+ callingUid == Process.SYSTEM_UID ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+ : ZenModeConfig.UPDATE_ORIGIN_APP,
+ "zmc.onServiceAdded:" + component, callingUid);
}
@Override
@@ -121,8 +123,10 @@
ZenModeConfig config = mHelper.getConfig();
if (config == null) return;
final int callingUid = Binder.getCallingUid();
- mHelper.setAutomaticZenRuleState(id, condition, callingUid,
- callingUid == Process.SYSTEM_UID);
+ mHelper.setAutomaticZenRuleState(id, condition,
+ callingUid == Process.SYSTEM_UID ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+ : ZenModeConfig.UPDATE_ORIGIN_APP,
+ callingUid);
}
// Only valid for CPS backed rules
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 218519f..7ec94c3 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1,4 +1,4 @@
-/**
+/*
* Copyright (c) 2014, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,11 +24,16 @@
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_UNKNOWN;
import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY;
import static android.service.notification.NotificationServiceProto.ROOT_CONFIG;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT_USER;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_RESTORE_BACKUP;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
import android.annotation.DrawableRes;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -79,6 +84,7 @@
import android.service.notification.DeviceEffectsApplier;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
import android.service.notification.ZenModeConfig.ZenRule;
import android.service.notification.ZenModeProto;
import android.service.notification.ZenPolicy;
@@ -111,8 +117,6 @@
import java.io.IOException;
import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -137,21 +141,6 @@
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
static final long SEND_ACTIVATION_AZR_STATUSES = 308673617L;
- /** A rule addition or update that is initiated by the System or SystemUI. */
- static final int FROM_SYSTEM_OR_SYSTEMUI = 1;
- /** A rule addition or update that is initiated by the user (through system settings). */
- static final int FROM_USER = 2;
- /** A rule addition or update that is initiated by an app (via NotificationManager APIs). */
- static final int FROM_APP = 3;
-
- @IntDef(prefix = { "FROM_" }, value = {
- FROM_SYSTEM_OR_SYSTEMUI,
- FROM_USER,
- FROM_APP
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface ChangeOrigin {}
-
// pkg|userId => uid
@VisibleForTesting protected final ArrayMap<String, Integer> mRulesUidCache = new ArrayMap<>();
@@ -160,7 +149,7 @@
private final SettingsObserver mSettingsObserver;
private final AppOpsManager mAppOps;
private final NotificationManager mNotificationManager;
- private ZenModeConfig mDefaultConfig;
+ private final ZenModeConfig mDefaultConfig;
private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
private final ZenModeFiltering mFiltering;
private final RingerModeDelegate mRingerModeDelegate = new
@@ -275,9 +264,8 @@
// "update" config to itself, which will have no effect in the case where a config
// was read in via XML, but will initialize zen mode if nothing was read in and the
// config remains the default.
- updateConfigAndZenModeLocked(mConfig, "init", true /*setRingerMode*/,
- Process.SYSTEM_UID /* callingUid */, true /* is system */,
- false /* no broadcasts*/);
+ updateConfigAndZenModeLocked(mConfig, UPDATE_ORIGIN_INIT, "init",
+ true /*setRingerMode*/, Process.SYSTEM_UID /* callingUid */);
}
}
@@ -297,24 +285,20 @@
/**
* Set the {@link DeviceEffectsApplier} used to apply the consolidated effects.
*
- * <p>If effects were calculated previously (for example, when we loaded a {@link ZenModeConfig}
- * that includes activated rules), they will be applied immediately.
+ * <p>Previously calculated effects (as loaded from the user's {@link ZenModeConfig}) will be
+ * applied immediately.
*/
void setDeviceEffectsApplier(@NonNull DeviceEffectsApplier deviceEffectsApplier) {
if (!Flags.modesApi()) {
return;
}
- ZenDeviceEffects consolidatedDeviceEffects;
synchronized (mConfigLock) {
if (mDeviceEffectsApplier != null) {
throw new IllegalStateException("Already set up a DeviceEffectsApplier!");
}
mDeviceEffectsApplier = deviceEffectsApplier;
- consolidatedDeviceEffects = mConsolidatedDeviceEffects;
}
- if (consolidatedDeviceEffects.hasEffects()) {
- applyConsolidatedDeviceEffects();
- }
+ applyConsolidatedDeviceEffects(UPDATE_ORIGIN_INIT);
}
public void onUserSwitched(int user) {
@@ -353,7 +337,8 @@
config.user = user;
}
synchronized (mConfigLock) {
- setConfigLocked(config, null, reason, Process.SYSTEM_UID, true);
+ setConfigLocked(config, null, UPDATE_ORIGIN_INIT_USER, reason,
+ Process.SYSTEM_UID);
}
cleanUpZenRules();
}
@@ -366,9 +351,11 @@
boolean fromSystemOrSystemUi) {
final int newZen = NotificationManager.zenModeFromInterruptionFilter(filter, -1);
if (newZen != -1) {
- setManualZenMode(newZen, null, name != null ? name.getPackageName() : null,
- "listener:" + (name != null ? name.flattenToShortString() : null),
- callingUid, fromSystemOrSystemUi);
+ setManualZenMode(newZen, null,
+ fromSystemOrSystemUi ? UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI : UPDATE_ORIGIN_APP,
+ /* reason= */ "listener:" + (name != null ? name.flattenToShortString() : null),
+ /* caller= */ name != null ? name.getPackageName() : null,
+ callingUid);
}
}
@@ -428,7 +415,7 @@
}
public String addAutomaticZenRule(String pkg, AutomaticZenRule automaticZenRule,
- String reason, int callingUid, @ChangeOrigin int origin) {
+ @ConfigChangeOrigin int origin, String reason, int callingUid) {
if (!ZenModeConfig.SYSTEM_AUTHORITY.equals(pkg)) {
PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner());
if (component == null) {
@@ -462,10 +449,9 @@
}
newConfig = mConfig.copy();
ZenRule rule = new ZenRule();
- populateZenRule(pkg, automaticZenRule, rule, true, origin);
+ populateZenRule(pkg, automaticZenRule, rule, origin, /* isNew= */ true);
newConfig.automaticRules.put(rule.id, rule);
- if (setConfigLocked(newConfig, reason, rule.component, true, callingUid,
- origin == FROM_SYSTEM_OR_SYSTEMUI)) {
+ if (setConfigLocked(newConfig, origin, reason, rule.component, true, callingUid)) {
return rule.id;
} else {
throw new AndroidRuntimeException("Could not create rule");
@@ -474,7 +460,7 @@
}
public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZenRule,
- String reason, int callingUid, @ChangeOrigin int origin) {
+ @ConfigChangeOrigin int origin, String reason, int callingUid) {
ZenModeConfig newConfig;
synchronized (mConfigLock) {
if (mConfig == null) return false;
@@ -502,9 +488,9 @@
}
}
- populateZenRule(rule.pkg, automaticZenRule, rule, false, origin);
- return setConfigLocked(newConfig, reason, rule.component, true, callingUid,
- origin == FROM_SYSTEM_OR_SYSTEMUI);
+ populateZenRule(rule.pkg, automaticZenRule, rule, origin, /* isNew= */ false);
+ return setConfigLocked(newConfig, origin, reason,
+ rule.component, true, callingUid);
}
}
@@ -541,8 +527,7 @@
Condition deactivated = new Condition(rule.conditionId,
mContext.getString(R.string.zen_mode_implicit_deactivated),
Condition.STATE_FALSE);
- setAutomaticZenRuleState(rule.id, deactivated,
- callingUid, /* fromSystemOrSystemUi= */ false);
+ setAutomaticZenRuleState(rule.id, deactivated, UPDATE_ORIGIN_APP, callingUid);
}
} else {
// Either create a new rule with a default ZenPolicy, or update an existing rule's
@@ -558,9 +543,8 @@
rule.condition = new Condition(rule.conditionId,
mContext.getString(R.string.zen_mode_implicit_activated),
Condition.STATE_TRUE);
- setConfigLocked(newConfig, /* triggeringComponent= */ null,
- "applyGlobalZenModeAsImplicitZenRule",
- callingUid, /* fromSystemOrSystemUi= */ false);
+ setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP,
+ "applyGlobalZenModeAsImplicitZenRule", callingUid);
}
}
}
@@ -595,9 +579,8 @@
}
// TODO: b/308673679 - Keep user customization of this rule!
rule.zenPolicy = ZenAdapters.notificationPolicyToZenPolicy(policy);
- setConfigLocked(newConfig, /* triggeringComponent= */ null,
- "applyGlobalPolicyAsImplicitZenRule",
- callingUid, /* fromSystemOrSystemUi= */ false);
+ setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP,
+ "applyGlobalPolicyAsImplicitZenRule", callingUid);
}
}
@@ -669,8 +652,8 @@
return "implicit_" + forPackage;
}
- public boolean removeAutomaticZenRule(String id, String reason, int callingUid,
- boolean fromSystemOrSystemUi) {
+ boolean removeAutomaticZenRule(String id, @ConfigChangeOrigin int origin, String reason,
+ int callingUid) {
ZenModeConfig newConfig;
synchronized (mConfigLock) {
if (mConfig == null) return false;
@@ -695,13 +678,12 @@
}
dispatchOnAutomaticRuleStatusChanged(
mConfig.user, ruleToRemove.getPkg(), id, AUTOMATIC_RULE_STATUS_REMOVED);
- return setConfigLocked(newConfig, reason, null, true, callingUid,
- fromSystemOrSystemUi);
+ return setConfigLocked(newConfig, origin, reason, null, true, callingUid);
}
}
- public boolean removeAutomaticZenRules(String packageName, String reason, int callingUid,
- boolean fromSystemOrSystemUi) {
+ boolean removeAutomaticZenRules(String packageName, @ConfigChangeOrigin int origin,
+ String reason, int callingUid) {
ZenModeConfig newConfig;
synchronized (mConfigLock) {
if (mConfig == null) return false;
@@ -712,13 +694,12 @@
newConfig.automaticRules.removeAt(i);
}
}
- return setConfigLocked(newConfig, reason, null, true, callingUid,
- fromSystemOrSystemUi);
+ return setConfigLocked(newConfig, origin, reason, null, true, callingUid);
}
}
- public void setAutomaticZenRuleState(String id, Condition condition, int callingUid,
- boolean fromSystemOrSystemUi) {
+ void setAutomaticZenRuleState(String id, Condition condition, @ConfigChangeOrigin int origin,
+ int callingUid) {
ZenModeConfig newConfig;
synchronized (mConfigLock) {
if (mConfig == null) return;
@@ -726,13 +707,12 @@
newConfig = mConfig.copy();
ArrayList<ZenRule> rules = new ArrayList<>();
rules.add(newConfig.automaticRules.get(id));
- setAutomaticZenRuleStateLocked(newConfig, rules, condition, callingUid,
- fromSystemOrSystemUi);
+ setAutomaticZenRuleStateLocked(newConfig, rules, condition, origin, callingUid);
}
}
- public void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition, int callingUid,
- boolean fromSystemOrSystemUi) {
+ void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition,
+ @ConfigChangeOrigin int origin, int callingUid) {
ZenModeConfig newConfig;
synchronized (mConfigLock) {
if (mConfig == null) return;
@@ -740,20 +720,23 @@
setAutomaticZenRuleStateLocked(newConfig,
findMatchingRules(newConfig, ruleDefinition, condition),
- condition, callingUid, fromSystemOrSystemUi);
+ condition, origin, callingUid);
}
}
@GuardedBy("mConfigLock")
private void setAutomaticZenRuleStateLocked(ZenModeConfig config, List<ZenRule> rules,
- Condition condition, int callingUid, boolean fromSystemOrSystemUi) {
+ Condition condition, @ConfigChangeOrigin int origin, int callingUid) {
if (rules == null || rules.isEmpty()) return;
+ if (Flags.modesApi() && condition.source == Condition.SOURCE_USER_ACTION) {
+ origin = UPDATE_ORIGIN_USER; // Although coming from app, it's actually a user action.
+ }
+
for (ZenRule rule : rules) {
rule.condition = condition;
updateSnoozing(rule);
- setConfigLocked(config, rule.component, "conditionChanged", callingUid,
- fromSystemOrSystemUi);
+ setConfigLocked(config, rule.component, origin, "conditionChanged", callingUid);
}
}
@@ -857,7 +840,7 @@
// update default rule (if locale changed, name of rule will change)
currRule.name = defaultRule.name;
updateAutomaticZenRule(defaultRule.id, zenRuleToAutomaticZenRule(currRule),
- "locale changed", callingUid, FROM_SYSTEM_OR_SYSTEMUI);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "locale changed", callingUid);
}
}
}
@@ -899,13 +882,12 @@
return null;
}
- @VisibleForTesting
void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule,
- boolean isNew, @ChangeOrigin int origin) {
- // TODO: b/308671593,b/311406021 - Handle origins more precisely:
- // - FROM_USER can override anything and updates bitmask of user-modified fields;
- // - FROM_SYSTEM_OR_SYSTEMUI can override anything and preserves bitmask;
- // - FROM_APP can only update if not user-modified.
+ @ConfigChangeOrigin int origin, boolean isNew) {
+ // TODO: b/308671593,b/311406021 - Handle origins more precisely:
+ // - USER can override anything and updates bitmask of user-modified fields;
+ // - SYSTEM_OR_SYSTEMUI can override anything and preserves bitmask;
+ // - APP can only update if not user-modified.
if (rule.enabled != automaticZenRule.isEnabled()) {
rule.snoozing = false;
}
@@ -951,12 +933,12 @@
*/
@Nullable
private static ZenDeviceEffects fixZenDeviceEffects(@Nullable ZenDeviceEffects oldEffects,
- @Nullable ZenDeviceEffects newEffects, @ChangeOrigin int origin) {
+ @Nullable ZenDeviceEffects newEffects, @ConfigChangeOrigin int origin) {
// TODO: b/308671593,b/311406021 - Handle origins more precisely:
- // - FROM_USER can override anything and updates bitmask of user-modified fields;
- // - FROM_SYSTEM_OR_SYSTEMUI can override anything and preserves bitmask;
- // - FROM_APP can only update if not user-modified.
- if (origin == FROM_SYSTEM_OR_SYSTEMUI || origin == FROM_USER) {
+ // - USER can override anything and updates bitmask of user-modified fields;
+ // - SYSTEM_OR_SYSTEMUI can override anything and preserves bitmask;
+ // - APP can only update if not user-modified.
+ if (origin != UPDATE_ORIGIN_APP) {
return newEffects;
}
@@ -1033,16 +1015,16 @@
: AUTOMATIC_RULE_STATUS_DISABLED);
}
- public void setManualZenMode(int zenMode, Uri conditionId, String caller, String reason,
- int callingUid, boolean fromSystemOrSystemUi) {
- setManualZenMode(zenMode, conditionId, reason, caller, true /*setRingerMode*/, callingUid,
- fromSystemOrSystemUi);
+ void setManualZenMode(int zenMode, Uri conditionId, @ConfigChangeOrigin int origin,
+ String reason, @Nullable String caller, int callingUid) {
+ setManualZenMode(zenMode, conditionId, origin, reason, caller, true /*setRingerMode*/,
+ callingUid);
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION, 0);
}
- private void setManualZenMode(int zenMode, Uri conditionId, String reason, String caller,
- boolean setRingerMode, int callingUid, boolean fromSystemOrSystemUi) {
+ private void setManualZenMode(int zenMode, Uri conditionId, @ConfigChangeOrigin int origin,
+ String reason, @Nullable String caller, boolean setRingerMode, int callingUid) {
ZenModeConfig newConfig;
synchronized (mConfigLock) {
if (mConfig == null) return;
@@ -1069,8 +1051,7 @@
}
newConfig.manualRule = newRule;
}
- setConfigLocked(newConfig, reason, null, setRingerMode, callingUid,
- fromSystemOrSystemUi);
+ setConfigLocked(newConfig, origin, reason, null, setRingerMode, callingUid);
}
}
@@ -1199,7 +1180,9 @@
}
if (DEBUG) Log.d(TAG, reason);
synchronized (mConfigLock) {
- setConfigLocked(config, null, reason, Process.SYSTEM_UID, true);
+ setConfigLocked(config, null,
+ forRestore ? UPDATE_ORIGIN_RESTORE_BACKUP : UPDATE_ORIGIN_INIT, reason,
+ Process.SYSTEM_UID);
}
}
}
@@ -1233,13 +1216,13 @@
/**
* Sets the global notification policy used for priority only do not disturb
*/
- public void setNotificationPolicy(Policy policy, int callingUid, boolean fromSystemOrSystemUi) {
+ public void setNotificationPolicy(Policy policy, @ConfigChangeOrigin int origin,
+ int callingUid) {
synchronized (mConfigLock) {
if (policy == null || mConfig == null) return;
final ZenModeConfig newConfig = mConfig.copy();
newConfig.applyNotificationPolicy(policy);
- setConfigLocked(newConfig, null, "setNotificationPolicy", callingUid,
- fromSystemOrSystemUi);
+ setConfigLocked(newConfig, null, origin, "setNotificationPolicy", callingUid);
}
}
@@ -1264,8 +1247,8 @@
}
}
}
- setConfigLocked(newConfig, null, "cleanUpZenRules", Process.SYSTEM_UID,
- true);
+ setConfigLocked(newConfig, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "cleanUpZenRules",
+ Process.SYSTEM_UID);
}
}
@@ -1287,22 +1270,22 @@
@GuardedBy("mConfigLock")
private boolean setConfigLocked(ZenModeConfig config, ComponentName triggeringComponent,
- String reason, int callingUid, boolean fromSystemOrSystemUi) {
- return setConfigLocked(config, reason, triggeringComponent, true /*setRingerMode*/,
- callingUid, fromSystemOrSystemUi);
+ @ConfigChangeOrigin int origin, String reason, int callingUid) {
+ return setConfigLocked(config, origin, reason, triggeringComponent, true /*setRingerMode*/,
+ callingUid);
}
- public void setConfig(ZenModeConfig config, ComponentName triggeringComponent, String reason,
- int callingUid, boolean fromSystemOrSystemUi) {
+ void setConfig(ZenModeConfig config, ComponentName triggeringComponent,
+ @ConfigChangeOrigin int origin, String reason, int callingUid) {
synchronized (mConfigLock) {
- setConfigLocked(config, triggeringComponent, reason, callingUid, fromSystemOrSystemUi);
+ setConfigLocked(config, triggeringComponent, origin, reason, callingUid);
}
}
@GuardedBy("mConfigLock")
- private boolean setConfigLocked(ZenModeConfig config, String reason,
- ComponentName triggeringComponent, boolean setRingerMode, int callingUid,
- boolean fromSystemOrSystemUi) {
+ private boolean setConfigLocked(ZenModeConfig config, @ConfigChangeOrigin int origin,
+ String reason, ComponentName triggeringComponent, boolean setRingerMode,
+ int callingUid) {
final long identity = Binder.clearCallingIdentity();
try {
if (config == null || !config.isValid()) {
@@ -1332,8 +1315,7 @@
if (policyChanged) {
dispatchOnPolicyChanged();
}
- updateConfigAndZenModeLocked(config, reason, setRingerMode, callingUid,
- fromSystemOrSystemUi, true);
+ updateConfigAndZenModeLocked(config, origin, reason, setRingerMode, callingUid);
mConditions.evaluateConfig(config, triggeringComponent, true /*processSubscriptions*/);
return true;
} catch (SecurityException e) {
@@ -1349,17 +1331,16 @@
* If logging is enabled, will also request logging of the outcome of this change if needed.
*/
@GuardedBy("mConfigLock")
- private void updateConfigAndZenModeLocked(ZenModeConfig config, String reason,
- boolean setRingerMode, int callingUid, boolean fromSystemOrSystemUi,
- boolean sendBroadcasts) {
+ private void updateConfigAndZenModeLocked(ZenModeConfig config, @ConfigChangeOrigin int origin,
+ String reason, boolean setRingerMode, int callingUid) {
final boolean logZenModeEvents = mFlagResolver.isEnabled(
SystemUiSystemPropertiesFlags.NotificationFlags.LOG_DND_STATE_EVENTS);
// Store (a copy of) all config and zen mode info prior to any changes taking effect
ZenModeEventLogger.ZenModeInfo prevInfo = new ZenModeEventLogger.ZenModeInfo(
mZenMode, mConfig, mConsolidatedPolicy);
if (!config.equals(mConfig)) {
- // schedule broadcasts
- if (Flags.modesApi() && sendBroadcasts) {
+ // Schedule broadcasts. Cannot be sent during boot, though.
+ if (Flags.modesApi() && origin != UPDATE_ORIGIN_INIT) {
for (ZenRule rule : config.automaticRules.values()) {
ZenRule original = mConfig.automaticRules.get(rule.id);
if (original != null) {
@@ -1377,15 +1358,19 @@
mConfig = config;
dispatchOnConfigChanged();
- updateAndApplyConsolidatedPolicyAndDeviceEffects(reason);
+ updateAndApplyConsolidatedPolicyAndDeviceEffects(origin, reason);
}
final String val = Integer.toString(config.hashCode());
Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
- evaluateZenModeLocked(reason, setRingerMode);
+ evaluateZenModeLocked(origin, reason, setRingerMode);
// After all changes have occurred, log if requested
if (logZenModeEvents) {
ZenModeEventLogger.ZenModeInfo newInfo = new ZenModeEventLogger.ZenModeInfo(
mZenMode, mConfig, mConsolidatedPolicy);
+ boolean fromSystemOrSystemUi = origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+ || origin == UPDATE_ORIGIN_INIT
+ || origin == UPDATE_ORIGIN_INIT_USER
+ || origin == UPDATE_ORIGIN_RESTORE_BACKUP;
mZenModeEventLogger.maybeLogZenChange(prevInfo, newInfo, callingUid,
fromSystemOrSystemUi);
}
@@ -1416,7 +1401,8 @@
@VisibleForTesting
@GuardedBy("mConfigLock")
- protected void evaluateZenModeLocked(String reason, boolean setRingerMode) {
+ protected void evaluateZenModeLocked(@ConfigChangeOrigin int origin, String reason,
+ boolean setRingerMode) {
if (DEBUG) Log.d(TAG, "evaluateZenMode");
if (mConfig == null) return;
final int policyHashBefore = mConsolidatedPolicy == null ? 0
@@ -1426,7 +1412,7 @@
ZenLog.traceSetZenMode(zen, reason);
mZenMode = zen;
setZenModeSetting(mZenMode);
- updateAndApplyConsolidatedPolicyAndDeviceEffects(reason);
+ updateAndApplyConsolidatedPolicyAndDeviceEffects(origin, reason);
boolean shouldApplyToRinger = setRingerMode && (zen != zenBefore || (
zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
&& policyHashBefore != mConsolidatedPolicy.hashCode()));
@@ -1468,6 +1454,7 @@
}
}
+ @GuardedBy("mConfigLock")
private void applyCustomPolicy(ZenPolicy policy, ZenRule rule) {
if (rule.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) {
policy.apply(new ZenPolicy.Builder()
@@ -1487,7 +1474,9 @@
}
}
- private void updateAndApplyConsolidatedPolicyAndDeviceEffects(String reason) {
+ @GuardedBy("mConfigLock")
+ private void updateAndApplyConsolidatedPolicyAndDeviceEffects(@ConfigChangeOrigin int origin,
+ String reason) {
synchronized (mConfigLock) {
if (mConfig == null) return;
ZenPolicy policy = new ZenPolicy();
@@ -1519,13 +1508,13 @@
ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build();
if (!deviceEffects.equals(mConsolidatedDeviceEffects)) {
mConsolidatedDeviceEffects = deviceEffects;
- mHandler.postApplyDeviceEffects();
+ mHandler.postApplyDeviceEffects(origin);
}
}
}
}
- private void applyConsolidatedDeviceEffects() {
+ private void applyConsolidatedDeviceEffects(@ConfigChangeOrigin int source) {
if (!Flags.modesApi()) {
return;
}
@@ -1536,7 +1525,7 @@
effects = mConsolidatedDeviceEffects;
}
if (applier != null) {
- applier.apply(effects);
+ applier.apply(effects, source);
}
}
@@ -1801,8 +1790,8 @@
}
@Override
- public int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew, String caller,
- int ringerModeExternal, VolumePolicy policy) {
+ public int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew,
+ @Nullable String caller, int ringerModeExternal, VolumePolicy policy) {
final boolean isChange = ringerModeOld != ringerModeNew;
int ringerModeExternalOut = ringerModeNew;
@@ -1839,8 +1828,9 @@
}
if (newZen != -1) {
- setManualZenMode(newZen, null, "ringerModeInternal", null,
- false /*setRingerMode*/, Process.SYSTEM_UID, true);
+ setManualZenMode(newZen, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+ "ringerModeInternal", /* caller= */ null, /* setRingerMode= */ false,
+ Process.SYSTEM_UID);
}
if (isChange || newZen != -1 || ringerModeExternal != ringerModeExternalOut) {
ZenLog.traceSetRingerModeInternal(ringerModeOld, ringerModeNew, caller,
@@ -1856,8 +1846,8 @@
}
@Override
- public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller,
- int ringerModeInternal, VolumePolicy policy) {
+ public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew,
+ @Nullable String caller, int ringerModeInternal, VolumePolicy policy) {
int ringerModeInternalOut = ringerModeNew;
final boolean isChange = ringerModeOld != ringerModeNew;
final boolean isVibrate = ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
@@ -1883,8 +1873,8 @@
break;
}
if (newZen != -1) {
- setManualZenMode(newZen, null, "ringerModeExternal", caller,
- false /*setRingerMode*/, Process.SYSTEM_UID, true);
+ setManualZenMode(newZen, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+ "ringerModeExternal", caller, false /*setRingerMode*/, Process.SYSTEM_UID);
}
ZenLog.traceSetRingerModeExternal(ringerModeOld, ringerModeNew, caller,
@@ -2024,11 +2014,9 @@
}
try {
final Resources res = mPm.getResourcesForApplication(packageName);
- final String fullName = res.getResourceName(resId);
-
- return fullName;
+ return res.getResourceName(resId);
} catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
- Log.e(TAG, "Resource name for ID=" + resId + " not found in package " + packageName
+ Slog.e(TAG, "Resource name for ID=" + resId + " not found in package " + packageName
+ ". Resource IDs may change when the application is upgraded, and the system"
+ " may not be able to find the correct resource.");
return null;
@@ -2149,9 +2137,9 @@
sendMessage(obtainMessage(MSG_RINGER_AUDIO, shouldApplyToRinger));
}
- private void postApplyDeviceEffects() {
+ private void postApplyDeviceEffects(@ConfigChangeOrigin int origin) {
removeMessages(MSG_APPLY_EFFECTS);
- sendEmptyMessage(MSG_APPLY_EFFECTS);
+ sendMessage(obtainMessage(MSG_APPLY_EFFECTS, origin, 0));
}
@Override
@@ -2168,7 +2156,8 @@
updateRingerAndAudio(shouldApplyToRinger);
break;
case MSG_APPLY_EFFECTS:
- applyConsolidatedDeviceEffects();
+ @ConfigChangeOrigin int origin = msg.arg1;
+ applyConsolidatedDeviceEffects(origin);
break;
}
}
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index 92d469c..c36c8ca 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -405,9 +405,19 @@
boolean isInstallDisabledForPackage(@NonNull String packageName, int uid,
@UserIdInt int userId);
- @Nullable
- List<VersionedPackage> getPackagesUsingSharedLibrary(@NonNull SharedLibraryInfo libInfo,
- @PackageManager.PackageInfoFlagsBits long flags, int callingUid, @UserIdInt int userId);
+ /**
+ * Returns a Pair that contains a list of packages that depend on the target library and the
+ * package library dependency information. The List<VersionedPackage> indicates a list of
+ * packages that depend on the target library, it may be null if no package depends on
+ * the target library. The List<Boolean> indicates whether each VersionedPackage in
+ * the List<VersionedPackage> optionally depends on the target library, where true means
+ * optional and false means required. It may be null if no package depends on
+ * the target library or without dependency information, e.g. uses-static-library.
+ */
+ @NonNull
+ Pair<List<VersionedPackage>, List<Boolean>> getPackagesUsingSharedLibrary(
+ @NonNull SharedLibraryInfo libInfo, @PackageManager.PackageInfoFlagsBits long flags,
+ int callingUid, @UserIdInt int userId);
@Nullable
ParceledListSlice<SharedLibraryInfo> getDeclaredSharedLibraries(
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 139c7c0..2ae1005 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -895,6 +895,9 @@
@PackageManager.ResolveInfoFlagsBits long flags, int filterCallingUid, int userId) {
ParsedActivity a = mComponentResolver.getActivity(component);
+ // Allow to match activities of quarantined packages.
+ flags |= PackageManager.MATCH_QUARANTINED_COMPONENTS;
+
if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a);
AndroidPackage pkg = a == null ? null : mPackages.get(a.getPackageName());
@@ -3862,11 +3865,13 @@
Binder.restoreCallingIdentity(identity);
}
+ var usingSharedLibraryPair =
+ getPackagesUsingSharedLibrary(libInfo, flags, callingUid, userId);
SharedLibraryInfo resLibInfo = new SharedLibraryInfo(libInfo.getPath(),
libInfo.getPackageName(), libInfo.getAllCodePaths(),
libInfo.getName(), libInfo.getLongVersion(),
libInfo.getType(), declaringPackage,
- getPackagesUsingSharedLibrary(libInfo, flags, callingUid, userId),
+ usingSharedLibraryPair.first,
(libInfo.getDependencies() == null
? null
: new ArrayList<>(libInfo.getDependencies())),
@@ -3935,13 +3940,15 @@
return false;
}
+
@Override
- public List<VersionedPackage> getPackagesUsingSharedLibrary(@NonNull SharedLibraryInfo libInfo,
- @PackageManager.PackageInfoFlagsBits long flags, int callingUid,
- @UserIdInt int userId) {
+ public Pair<List<VersionedPackage>, List<Boolean>> getPackagesUsingSharedLibrary(
+ @NonNull SharedLibraryInfo libInfo, @PackageManager.PackageInfoFlagsBits long flags,
+ int callingUid, @UserIdInt int userId) {
List<VersionedPackage> versionedPackages = null;
final ArrayMap<String, ? extends PackageStateInternal> packageStates = getPackageStates();
final int packageCount = packageStates.size();
+ List<Boolean> usesLibsOptional = null;
for (int i = 0; i < packageCount; i++) {
PackageStateInternal ps = packageStates.valueAt(i);
if (ps == null) {
@@ -3958,12 +3965,15 @@
libInfo.isStatic() ? ps.getUsesStaticLibraries() : ps.getUsesSdkLibraries();
final long[] libsVersions = libInfo.isStatic() ? ps.getUsesStaticLibrariesVersions()
: ps.getUsesSdkLibrariesVersionsMajor();
+ final boolean[] libsOptional = libInfo.isSdk()
+ ? ps.getUsesSdkLibrariesOptional() : null;
final int libIdx = ArrayUtils.indexOf(libs, libName);
if (libIdx < 0) {
continue;
}
if (libsVersions[libIdx] != libInfo.getLongVersion()) {
+ // Not expected StaticLib/SdkLib version
continue;
}
if (shouldFilterApplication(ps, callingUid, userId)) {
@@ -3972,6 +3982,9 @@
if (versionedPackages == null) {
versionedPackages = new ArrayList<>();
}
+ if (usesLibsOptional == null) {
+ usesLibsOptional = new ArrayList<>();
+ }
// If the dependent is a static shared lib, use the public package name
String dependentPackageName = ps.getPackageName();
if (ps.getPkg() != null && ps.getPkg().isStaticSharedLibrary()) {
@@ -3979,6 +3992,7 @@
}
versionedPackages.add(new VersionedPackage(dependentPackageName,
ps.getVersionCode()));
+ usesLibsOptional.add(libsOptional != null && libsOptional[libIdx]);
} else if (ps.getPkg() != null) {
if (ArrayUtils.contains(ps.getPkg().getUsesLibraries(), libName)
|| ArrayUtils.contains(ps.getPkg().getUsesOptionalLibraries(), libName)) {
@@ -3994,7 +4008,7 @@
}
}
- return versionedPackages;
+ return new Pair<>(versionedPackages, usesLibsOptional);
}
@Nullable
@@ -4053,13 +4067,14 @@
Binder.restoreCallingIdentity(identity);
}
+ var usingSharedLibraryPair =
+ getPackagesUsingSharedLibrary(libraryInfo, flags, callingUid, userId);
SharedLibraryInfo resultLibraryInfo = new SharedLibraryInfo(
libraryInfo.getPath(), libraryInfo.getPackageName(),
libraryInfo.getAllCodePaths(), libraryInfo.getName(),
libraryInfo.getLongVersion(), libraryInfo.getType(),
libraryInfo.getDeclaringPackage(),
- getPackagesUsingSharedLibrary(
- libraryInfo, flags, callingUid, userId),
+ usingSharedLibraryPair.first,
libraryInfo.getDependencies() == null
? null : new ArrayList<>(libraryInfo.getDependencies()),
libraryInfo.isNative());
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 80e6c83..93836266 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -18,7 +18,6 @@
import static android.Manifest.permission.CONTROL_KEYGUARD;
import static android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS;
-import static android.content.pm.Flags.sdkLibIndependence;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
@@ -40,6 +39,7 @@
import android.app.ApplicationPackageManager;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
import android.content.pm.IPackageDeleteObserver2;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
@@ -72,8 +72,6 @@
import dalvik.system.VMRuntime;
-import java.util.List;
-
/**
* Deletes a package. Uninstall if installed, or at least deletes the base directory if it's called
* from a failed installation. Fixes user state after deletion.
@@ -181,16 +179,36 @@
}
if (libraryInfo != null) {
+ boolean flagSdkLibIndependence = Flags.sdkLibIndependence();
for (int currUserId : allUsers) {
if (removeUser != UserHandle.USER_ALL && removeUser != currUserId) {
continue;
}
- List<VersionedPackage> libClientPackages =
- computer.getPackagesUsingSharedLibrary(libraryInfo,
- MATCH_KNOWN_PACKAGES, Process.SYSTEM_UID, currUserId);
- boolean allowSdkLibIndependence =
- (pkg.getSdkLibraryName() != null) && sdkLibIndependence();
- if (!ArrayUtils.isEmpty(libClientPackages) && !allowSdkLibIndependence) {
+ var libClientPackagesPair = computer.getPackagesUsingSharedLibrary(
+ libraryInfo, MATCH_KNOWN_PACKAGES, Process.SYSTEM_UID, currUserId);
+ var libClientPackages = libClientPackagesPair.first;
+ var libClientOptional = libClientPackagesPair.second;
+ // We by default don't allow removing a package if the host lib is still be
+ // used by other client packages
+ boolean allowLibIndependence = false;
+ // Only when the sdkLibIndependence flag is enabled we will respect the
+ // "optional" attr in uses-sdk-library. Only allow to remove sdk-lib host
+ // package if no required clients depend on it
+ if ((pkg.getSdkLibraryName() != null)
+ && !ArrayUtils.isEmpty(libClientPackages)
+ && !ArrayUtils.isEmpty(libClientOptional)
+ && (libClientPackages.size() == libClientOptional.size())
+ && flagSdkLibIndependence) {
+ allowLibIndependence = true;
+ for (int i = 0; i < libClientPackages.size(); i++) {
+ boolean usesSdkLibOptional = libClientOptional.get(i);
+ if (!usesSdkLibOptional) {
+ allowLibIndependence = false;
+ break;
+ }
+ }
+ }
+ if (!ArrayUtils.isEmpty(libClientPackages) && !allowLibIndependence) {
Slog.w(TAG, "Not removing package " + pkg.getManifestPackageName()
+ " hosting lib " + libraryInfo.getName() + " version "
+ libraryInfo.getLongVersion() + " used by " + libClientPackages
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f27c462..2880f84 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -7113,9 +7113,9 @@
if (info == null) {
continue;
}
- final List<VersionedPackage> dependents =
- computer.getPackagesUsingSharedLibrary(info, 0, Process.SYSTEM_UID,
- userId);
+ var usingSharedLibraryPair = computer.getPackagesUsingSharedLibrary(info, 0,
+ Process.SYSTEM_UID, userId);
+ final List<VersionedPackage> dependents = usingSharedLibraryPair.first;
if (dependents == null) {
continue;
}
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 174df44..7d0a1f6 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -123,11 +123,15 @@
@Nullable
private Map<String, Set<String>> mimeGroups;
+ // TODO(b/314036181): encapsulate all these fields for usesSdk, instead of having three
+ // separate arrays.
@Nullable
private String[] usesSdkLibraries;
@Nullable
private long[] usesSdkLibrariesVersionsMajor;
+ @Nullable
+ private boolean[] usesSdkLibrariesOptional;
@Nullable
private String[] usesStaticLibraries;
@@ -701,6 +705,9 @@
usesSdkLibrariesVersionsMajor = other.usesSdkLibrariesVersionsMajor != null
? Arrays.copyOf(other.usesSdkLibrariesVersionsMajor,
other.usesSdkLibrariesVersionsMajor.length) : null;
+ usesSdkLibrariesOptional = other.usesSdkLibrariesOptional != null
+ ? Arrays.copyOf(other.usesSdkLibrariesOptional,
+ other.usesSdkLibrariesOptional.length) : null;
usesStaticLibraries = other.usesStaticLibraries != null
? Arrays.copyOf(other.usesStaticLibraries,
@@ -1344,6 +1351,12 @@
@NonNull
@Override
+ public boolean[] getUsesSdkLibrariesOptional() {
+ return usesSdkLibrariesOptional == null ? EmptyArray.BOOLEAN : usesSdkLibrariesOptional;
+ }
+
+ @NonNull
+ @Override
public String[] getUsesStaticLibraries() {
return usesStaticLibraries == null ? EmptyArray.STRING : usesStaticLibraries;
}
@@ -1444,6 +1457,12 @@
return this;
}
+ public PackageSetting setUsesSdkLibrariesOptional(boolean[] usesSdkLibrariesOptional) {
+ this.usesSdkLibrariesOptional = usesSdkLibrariesOptional;
+ onChanged();
+ return this;
+ }
+
public PackageSetting setUsesStaticLibraries(String[] usesStaticLibraries) {
this.usesStaticLibraries = usesStaticLibraries;
onChanged();
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 53b84e6..31a63e0 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -218,7 +218,8 @@
parsedPackage.getLongVersionCode(), pkgFlags, pkgPrivateFlags, user,
true /*allowInstall*/, instantApp, virtualPreload, isStoppedSystemApp,
UserManagerService.getInstance(), usesSdkLibraries,
- parsedPackage.getUsesSdkLibrariesVersionsMajor(), usesStaticLibraries,
+ parsedPackage.getUsesSdkLibrariesVersionsMajor(),
+ parsedPackage.getUsesSdkLibrariesOptional(), usesStaticLibraries,
parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
newDomainSetId,
parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
@@ -240,6 +241,7 @@
PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting),
UserManagerService.getInstance(),
usesSdkLibraries, parsedPackage.getUsesSdkLibrariesVersionsMajor(),
+ parsedPackage.getUsesSdkLibrariesOptional(),
usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(),
parsedPackage.getMimeGroups(), newDomainSetId,
parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 2cbf714..75d88da 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -106,6 +106,7 @@
import com.android.server.backup.PreferredActivityBackupHelper;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.parsing.PackageInfoUtils;
+import com.android.server.pm.parsing.pkg.PackageImpl;
import com.android.server.pm.permission.LegacyPermissionDataProvider;
import com.android.server.pm.permission.LegacyPermissionSettings;
import com.android.server.pm.permission.LegacyPermissionState;
@@ -339,6 +340,8 @@
private static final String ATTR_DISTRACTION_FLAGS = "distraction_flags";
private static final String ATTR_SUSPENDED = "suspended";
private static final String ATTR_SUSPENDING_PACKAGE = "suspending-package";
+
+ private static final String ATTR_OPTIONAL = "optional";
/**
* @deprecated Legacy attribute, kept only for upgrading from P builds.
*/
@@ -942,6 +945,7 @@
ret.setLongVersionCode(p.getVersionCode());
ret.setUsesSdkLibraries(p.getUsesSdkLibraries());
ret.setUsesSdkLibrariesVersionsMajor(p.getUsesSdkLibrariesVersionsMajor());
+ ret.setUsesSdkLibrariesOptional(p.getUsesSdkLibrariesOptional());
ret.setUsesStaticLibraries(p.getUsesStaticLibraries());
ret.setUsesStaticLibrariesVersions(p.getUsesStaticLibrariesVersions());
ret.setMimeGroups(p.getMimeGroups());
@@ -1061,9 +1065,9 @@
UserHandle installUser, boolean allowInstall, boolean instantApp,
boolean virtualPreload, boolean isStoppedSystemApp, UserManagerService userManager,
String[] usesSdkLibraries, long[] usesSdkLibrariesVersions,
- String[] usesStaticLibraries, long[] usesStaticLibrariesVersions,
- Set<String> mimeGroupNames, @NonNull UUID domainSetId,
- int targetSdkVersion, byte[] restrictUpdatedHash) {
+ boolean[] usesSdkLibrariesOptional, String[] usesStaticLibraries,
+ long[] usesStaticLibrariesVersions, Set<String> mimeGroupNames,
+ @NonNull UUID domainSetId, int targetSdkVersion, byte[] restrictUpdatedHash) {
final PackageSetting pkgSetting;
if (originalPkg != null) {
if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package "
@@ -1079,6 +1083,7 @@
.setLongVersionCode(versionCode)
.setUsesSdkLibraries(usesSdkLibraries)
.setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersions)
+ .setUsesSdkLibrariesOptional(usesSdkLibrariesOptional)
.setUsesStaticLibraries(usesStaticLibraries)
.setUsesStaticLibrariesVersions(usesStaticLibrariesVersions)
// Update new package state.
@@ -1096,6 +1101,7 @@
pkgPrivateFlags, domainSetId)
.setUsesSdkLibraries(usesSdkLibraries)
.setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersions)
+ .setUsesSdkLibrariesOptional(usesSdkLibrariesOptional)
.setUsesStaticLibraries(usesStaticLibraries)
.setUsesStaticLibrariesVersions(usesStaticLibrariesVersions)
.setLegacyNativeLibraryPath(legacyNativeLibraryPath)
@@ -1218,6 +1224,7 @@
@Nullable String primaryCpuAbi, @Nullable String secondaryCpuAbi, int pkgFlags,
int pkgPrivateFlags, @NonNull UserManagerService userManager,
@Nullable String[] usesSdkLibraries, @Nullable long[] usesSdkLibrariesVersions,
+ @Nullable boolean[] usesSdkLibrariesOptional,
@Nullable String[] usesStaticLibraries, @Nullable long[] usesStaticLibrariesVersions,
@Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId,
int targetSdkVersion, byte[] restrictUpdatedHash)
@@ -1277,12 +1284,17 @@
.setRestrictUpdateHash(restrictUpdatedHash);
// Update SDK library dependencies if needed.
if (usesSdkLibraries != null && usesSdkLibrariesVersions != null
- && usesSdkLibraries.length == usesSdkLibrariesVersions.length) {
+ && usesSdkLibrariesOptional != null
+ && usesSdkLibraries.length == usesSdkLibrariesVersions.length
+ && usesSdkLibraries.length == usesSdkLibrariesOptional.length) {
pkgSetting.setUsesSdkLibraries(usesSdkLibraries)
- .setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersions);
+ .setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersions)
+ .setUsesSdkLibrariesOptional(usesSdkLibrariesOptional);
} else {
pkgSetting.setUsesSdkLibraries(null)
- .setUsesSdkLibrariesVersionsMajor(null);
+ .setUsesSdkLibrariesVersionsMajor(null)
+ .setUsesSdkLibrariesOptional(null);
+
}
// Update static shared library dependencies if needed.
@@ -2537,12 +2549,15 @@
throws IOException, XmlPullParserException {
String libName = parser.getAttributeValue(null, ATTR_NAME);
long libVersion = parser.getAttributeLong(null, ATTR_VERSION, -1);
+ boolean optional = parser.getAttributeBoolean(null, ATTR_OPTIONAL, true);
if (libName != null && libVersion >= 0) {
outPs.setUsesSdkLibraries(ArrayUtils.appendElement(String.class,
outPs.getUsesSdkLibraries(), libName));
outPs.setUsesSdkLibrariesVersionsMajor(ArrayUtils.appendLong(
outPs.getUsesSdkLibrariesVersionsMajor(), libVersion));
+ outPs.setUsesSdkLibrariesOptional(PackageImpl.appendBoolean(
+ outPs.getUsesSdkLibrariesOptional(), optional));
}
XmlUtils.skipCurrentTag(parser);
@@ -2564,7 +2579,7 @@
}
void writeUsesSdkLibLPw(TypedXmlSerializer serializer, String[] usesSdkLibraries,
- long[] usesSdkLibraryVersions) throws IOException {
+ long[] usesSdkLibraryVersions, boolean[] usesSdkLibrariesOptional) throws IOException {
if (ArrayUtils.isEmpty(usesSdkLibraries) || ArrayUtils.isEmpty(usesSdkLibraryVersions)
|| usesSdkLibraries.length != usesSdkLibraryVersions.length) {
return;
@@ -2573,9 +2588,11 @@
for (int i = 0; i < libCount; i++) {
final String libName = usesSdkLibraries[i];
final long libVersion = usesSdkLibraryVersions[i];
+ boolean libOptional = usesSdkLibrariesOptional[i];
serializer.startTag(null, TAG_USES_SDK_LIB);
serializer.attribute(null, ATTR_NAME, libName);
serializer.attributeLong(null, ATTR_VERSION, libVersion);
+ serializer.attributeBoolean(null, ATTR_OPTIONAL, libOptional);
serializer.endTag(null, TAG_USES_SDK_LIB);
}
}
@@ -3106,7 +3123,8 @@
}
writeUsesSdkLibLPw(serializer, pkg.getUsesSdkLibraries(),
- pkg.getUsesSdkLibrariesVersionsMajor());
+ pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesOptional());
+
writeUsesStaticLibLPw(serializer, pkg.getUsesStaticLibraries(),
pkg.getUsesStaticLibrariesVersions());
@@ -3206,7 +3224,7 @@
}
writeUsesSdkLibLPw(serializer, pkg.getUsesSdkLibraries(),
- pkg.getUsesSdkLibrariesVersionsMajor());
+ pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesOptional());
writeUsesStaticLibLPw(serializer, pkg.getUsesStaticLibraries(),
pkg.getUsesStaticLibrariesVersions());
@@ -5091,12 +5109,14 @@
List<String> usesSdkLibraries = pkg.getUsesSdkLibraries();
long[] usesSdkLibrariesVersionsMajor = pkg.getUsesSdkLibrariesVersionsMajor();
+ boolean[] usesSdkLibrariesOptional = pkg.getUsesSdkLibrariesOptional();
if (usesSdkLibraries.size() > 0) {
pw.print(prefix); pw.println(" usesSdkLibraries:");
for (int i = 0, size = usesSdkLibraries.size(); i < size; ++i) {
pw.print(prefix); pw.print(" ");
pw.print(usesSdkLibraries.get(i)); pw.print(" version:");
- pw.println(usesSdkLibrariesVersionsMajor[i]);
+ pw.println(usesSdkLibrariesVersionsMajor[i]); pw.print(" optional:");
+ pw.println(usesSdkLibrariesOptional[i]);
}
}
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 9384c13..94495bf 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -16,7 +16,6 @@
package com.android.server.pm;
-import static android.content.pm.Flags.sdkLibIndependence;
import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_LIBRARY_BAD_CERTIFICATE_DIGEST;
@@ -28,6 +27,7 @@
import android.annotation.Nullable;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
+import android.content.pm.Flags;
import android.content.pm.PackageManager;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.Signature;
@@ -82,6 +82,8 @@
public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable, Snappable {
private static final boolean DEBUG_SHARED_LIBRARIES = false;
+ private static final String LIBRARY_TYPE_SDK = "sdk";
+
/**
* Apps targeting Android S and above need to declare dependencies to the public native
* shared libraries that are defined by the device maker using {@code uses-native-library} tag
@@ -798,8 +800,9 @@
// Remove the shared library overlays from its dependent packages.
for (int currentUserId : mPm.mUserManager.getUserIds()) {
- final List<VersionedPackage> dependents = snapshot.getPackagesUsingSharedLibrary(
- libraryInfo, 0, Process.SYSTEM_UID, currentUserId);
+ var usingSharedLibraryPair = snapshot.getPackagesUsingSharedLibrary(libraryInfo, 0,
+ Process.SYSTEM_UID, currentUserId);
+ final List<VersionedPackage> dependents = usingSharedLibraryPair.first;
if (dependents == null) {
continue;
}
@@ -921,42 +924,43 @@
// duplicates.
ArrayList<SharedLibraryInfo> usesLibraryInfos = null;
if (!pkg.getUsesLibraries().isEmpty()) {
- usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null,
+ usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null, null,
pkg.getPackageName(), "shared", true, pkg.getTargetSdkVersion(), null,
availablePackages, newLibraries);
}
if (!pkg.getUsesStaticLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesStaticLibraries(),
pkg.getUsesStaticLibrariesVersions(), pkg.getUsesStaticLibrariesCertDigests(),
- pkg.getPackageName(), "static shared", true, pkg.getTargetSdkVersion(),
- usesLibraryInfos, availablePackages, newLibraries);
+ null, pkg.getPackageName(), "static shared", true,
+ pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, newLibraries);
}
if (!pkg.getUsesOptionalLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalLibraries(), null, null,
- pkg.getPackageName(), "shared", false, pkg.getTargetSdkVersion(),
+ null, pkg.getPackageName(), "shared", false, pkg.getTargetSdkVersion(),
usesLibraryInfos, availablePackages, newLibraries);
}
if (platformCompat.isChangeEnabledInternal(ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES,
pkg.getPackageName(), pkg.getTargetSdkVersion())) {
if (!pkg.getUsesNativeLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesNativeLibraries(), null,
- null, pkg.getPackageName(), "native shared", true,
+ null, null, pkg.getPackageName(), "native shared", true,
pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages,
newLibraries);
}
if (!pkg.getUsesOptionalNativeLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalNativeLibraries(),
- null, null, pkg.getPackageName(), "native shared", false,
+ null, null, null, pkg.getPackageName(), "native shared", false,
pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages,
newLibraries);
}
}
if (!pkg.getUsesSdkLibraries().isEmpty()) {
// Allow installation even if sdk-library dependency doesn't exist
- boolean required = !sdkLibIndependence();
+ boolean required = !Flags.sdkLibIndependence();
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesSdkLibraries(),
pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesCertDigests(),
- pkg.getPackageName(), "sdk", required, pkg.getTargetSdkVersion(),
+ pkg.getUsesSdkLibrariesOptional(),
+ pkg.getPackageName(), LIBRARY_TYPE_SDK, required, pkg.getTargetSdkVersion(),
usesLibraryInfos, availablePackages, newLibraries);
}
return usesLibraryInfos;
@@ -965,6 +969,7 @@
private ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(
@NonNull List<String> requestedLibraries,
@Nullable long[] requiredVersions, @Nullable String[][] requiredCertDigests,
+ @Nullable boolean[] libsOptional,
@NonNull String packageName, @NonNull String libraryType, boolean required,
int targetSdk, @Nullable ArrayList<SharedLibraryInfo> outUsedLibraries,
@NonNull final Map<String, AndroidPackage> availablePackages,
@@ -981,7 +986,10 @@
libName, libVersion, mSharedLibraries, newLibraries);
}
if (libraryInfo == null) {
- if (required) {
+ // Only allow app be installed if the app specifies the sdk-library dependency is
+ // optional
+ if (required || (LIBRARY_TYPE_SDK.equals(libraryType) && (libsOptional != null
+ && !libsOptional[i]))) {
throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
"Package " + packageName + " requires unavailable " + libraryType
+ " library " + libName + "; failing!");
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index 71f6c0d..fe8c12c 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -33,7 +33,6 @@
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
-import android.provider.DeviceConfig;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IntArray;
@@ -504,10 +503,6 @@
final String requiredPermissionControllerPackage =
getKnownPackageName(snapshot, KnownPackages.PACKAGE_PERMISSION_CONTROLLER,
userId);
- final AppOpsManager appOpsManager = mInjector.getSystemService(AppOpsManager.class);
- final boolean isSystemExemptFlagEnabled = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
- SYSTEM_EXEMPT_FROM_SUSPENSION, /* defaultValue= */ true);
for (int i = 0; i < packageNames.length; i++) {
canSuspend[i] = false;
final String packageName = packageNames[i];
@@ -581,9 +576,7 @@
+ pkg.getStaticSharedLibraryName());
continue;
}
- if (isSystemExemptFlagEnabled && appOpsManager.checkOpNoThrow(
- AppOpsManager.OP_SYSTEM_EXEMPT_FROM_SUSPENSION, uid, packageName)
- == AppOpsManager.MODE_ALLOWED) {
+ if (exemptFromSuspensionByAppOp(uid, packageName)) {
Slog.w(TAG, "Cannot suspend package \"" + packageName
+ "\": has OP_SYSTEM_EXEMPT_FROM_SUSPENSION set");
continue;
@@ -601,6 +594,13 @@
return canSuspend;
}
+ private boolean exemptFromSuspensionByAppOp(int uid, String packageName) {
+ final AppOpsManager appOpsManager = mInjector.getSystemService(AppOpsManager.class);
+ return appOpsManager.checkOpNoThrow(
+ AppOpsManager.OP_SYSTEM_EXEMPT_FROM_SUSPENSION, uid, packageName)
+ == AppOpsManager.MODE_ALLOWED;
+ }
+
/**
* Suspends packages on behalf of an admin.
*
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b53a21c..a7b52f4 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1519,7 +1519,7 @@
try {
if (enableQuietMode) {
- ActivityManager.getService().stopUser(userId, /* force= */ true, null);
+ stopUserForQuietMode(userId);
LocalServices.getService(ActivityManagerInternal.class)
.killForegroundAppsForUser(userId);
} else {
@@ -1547,6 +1547,18 @@
}
}
+ private void stopUserForQuietMode(int userId) throws RemoteException {
+ if (android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) {
+ // Allow delayed locking since some profile types want to be able to unlock again via
+ // biometrics.
+ ActivityManager.getService()
+ .stopUserWithDelayedLocking(userId, /* force= */ true, null);
+ return;
+ }
+ ActivityManager.getService().stopUser(userId, /* force= */ true, null);
+ }
+
private void logQuietModeEnabled(@UserIdInt int userId, boolean enableQuietMode,
@Nullable String callingPackage) {
Slogf.i(LOG_TAG,
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 7f013b8..7386301 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -311,6 +311,7 @@
.setStartWithParent(true)
.setCredentialShareableWithParent(true)
.setAuthAlwaysRequiredToDisableQuietMode(true)
+ .setAllowStoppingUserWithDelayedLocking(true)
.setMediaSharedWithParent(false)
.setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
.setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index d642018..b23dbee 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -266,17 +266,20 @@
if ((flags & PackageManager.GET_ACTIVITIES) != 0) {
final int N = pkg.getActivities().size();
if (N > 0) {
+ // Allow to match activities of quarantined packages.
+ long aflags = flags | PackageManager.MATCH_QUARANTINED_COMPONENTS;
+
int num = 0;
final ActivityInfo[] res = new ActivityInfo[N];
for (int i = 0; i < N; i++) {
final ParsedActivity a = pkg.getActivities().get(i);
if (ComponentParseUtils.isMatch(state, pkgSetting.isSystem(), pkg.isEnabled(), a,
- flags)) {
+ aflags)) {
if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(
a.getName())) {
continue;
}
- res[num++] = generateActivityInfo(pkg, a, flags, state,
+ res[num++] = generateActivityInfo(pkg, a, aflags, state,
applicationInfo, userId, pkgSetting);
}
}
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
index 85d95ea..da58d47 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
@@ -267,6 +267,8 @@
@Nullable
private String[][] usesSdkLibrariesCertDigests;
@Nullable
+ private boolean[] usesSdkLibrariesOptional;
+ @Nullable
@DataClass.ParcelWith(ForInternedString.class)
private String sharedUserId;
private int sharedUserLabel;
@@ -718,16 +720,33 @@
@Override
public PackageImpl addUsesSdkLibrary(String libraryName, long versionMajor,
- String[] certSha256Digests) {
+ String[] certSha256Digests, boolean usesSdkLibrariesOptional) {
this.usesSdkLibraries = CollectionUtils.add(this.usesSdkLibraries,
TextUtils.safeIntern(libraryName));
this.usesSdkLibrariesVersionsMajor = ArrayUtils.appendLong(
this.usesSdkLibrariesVersionsMajor, versionMajor, true);
this.usesSdkLibrariesCertDigests = ArrayUtils.appendElement(String[].class,
this.usesSdkLibrariesCertDigests, certSha256Digests, true);
+ this.usesSdkLibrariesOptional = appendBoolean(this.usesSdkLibrariesOptional,
+ usesSdkLibrariesOptional);
return this;
}
+ /**
+ * Adds value to given array if not already present, providing set-like
+ * behavior.
+ */
+ public static boolean[] appendBoolean(@Nullable boolean[] cur, boolean val) {
+ if (cur == null) {
+ return new boolean[] { val };
+ }
+ final int N = cur.length;
+ boolean[] ret = new boolean[N + 1];
+ System.arraycopy(cur, 0, ret, 0, N);
+ ret[N] = val;
+ return ret;
+ }
+
@Override
public PackageImpl addUsesStaticLibrary(String libraryName, long version,
String[] certSha256Digests) {
@@ -1468,6 +1487,12 @@
@Override
public long[] getUsesSdkLibrariesVersionsMajor() { return usesSdkLibrariesVersionsMajor; }
+ @Nullable
+ @Override
+ public boolean[] getUsesSdkLibrariesOptional() {
+ return usesSdkLibrariesOptional;
+ }
+
@NonNull
@Override
public List<String> getUsesStaticLibraries() {
@@ -3126,6 +3151,7 @@
dest.writeStringArray(this.usesSdkLibrariesCertDigests[index]);
}
}
+ dest.writeBooleanArray(this.usesSdkLibrariesOptional);
sForInternedString.parcel(this.sharedUserId, dest, flags);
dest.writeInt(this.sharedUserLabel);
@@ -3278,6 +3304,7 @@
}
}
}
+ this.usesSdkLibrariesOptional = in.createBooleanArray();
this.sharedUserId = sForInternedString.unparcel(in);
this.sharedUserLabel = in.readInt();
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index 10b59c7..a7ae4eb 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -322,6 +322,14 @@
long[] getUsesSdkLibrariesVersionsMajor();
/**
+ * @see R.styleable#AndroidManifestUsesSdkLibrary_optional
+ * @hide
+ */
+ @Immutable.Ignore
+ @NonNull
+ boolean[] getUsesSdkLibrariesOptional();
+
+ /**
* @see R.styleable#AndroidManifestUsesStaticLibrary
* @hide
*/
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index 722350a..aa0fb27 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -2598,6 +2598,8 @@
R.styleable.AndroidManifestUsesSdkLibrary_versionMajor, -1);
String certSha256Digest = sa.getNonResourceString(R.styleable
.AndroidManifestUsesSdkLibrary_certDigest);
+ boolean optional =
+ sa.getBoolean(R.styleable.AndroidManifestUsesSdkLibrary_optional, false);
// Since an APK providing a static shared lib can only provide the lib - fail if
// malformed
@@ -2641,7 +2643,8 @@
System.arraycopy(additionalCertSha256Digests, 0, certSha256Digests,
1, additionalCertSha256Digests.length);
- return input.success(pkg.addUsesSdkLibrary(lname, versionMajor, certSha256Digests));
+ return input.success(
+ pkg.addUsesSdkLibrary(lname, versionMajor, certSha256Digests, optional));
} finally {
sa.recycle();
}
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 99653ae..24d7acd 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -759,6 +759,36 @@
case "NPU":
type = Temperature.TYPE_NPU;
break;
+ case "TPU":
+ type = Temperature.TYPE_TPU;
+ break;
+ case "DISPLAY":
+ type = Temperature.TYPE_DISPLAY;
+ break;
+ case "MODEM":
+ type = Temperature.TYPE_MODEM;
+ break;
+ case "SOC":
+ type = Temperature.TYPE_SOC;
+ break;
+ case "WIFI":
+ type = Temperature.TYPE_WIFI;
+ break;
+ case "CAMERA":
+ type = Temperature.TYPE_CAMERA;
+ break;
+ case "FLASHLIGHT":
+ type = Temperature.TYPE_FLASHLIGHT;
+ break;
+ case "SPEAKER":
+ type = Temperature.TYPE_SPEAKER;
+ break;
+ case "AMBIENT":
+ type = Temperature.TYPE_AMBIENT;
+ break;
+ case "POGO":
+ type = Temperature.TYPE_POGO;
+ break;
default:
pw.println("Invalid temperature type: " + typeName);
return -1;
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
index 43fd15d..6fbbc0f 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
@@ -19,7 +19,6 @@
import android.annotation.NonNull;
import android.os.BatteryConsumer;
-import com.android.internal.os.MultiStateStats;
import com.android.internal.os.PowerStats;
import java.lang.annotation.Retention;
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java
index 5fd8ddf..7feb964 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java
@@ -19,7 +19,6 @@
import android.annotation.Nullable;
import android.util.Log;
-import com.android.internal.os.MultiStateStats;
import com.android.internal.os.PowerStats;
import java.util.ArrayList;
diff --git a/core/java/com/android/internal/os/MultiStateStats.java b/services/core/java/com/android/server/power/stats/MultiStateStats.java
similarity index 99%
rename from core/java/com/android/internal/os/MultiStateStats.java
rename to services/core/java/com/android/server/power/stats/MultiStateStats.java
index ecfed53..9356950 100644
--- a/core/java/com/android/internal/os/MultiStateStats.java
+++ b/services/core/java/com/android/server/power/stats/MultiStateStats.java
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-package com.android.internal.os;
+package com.android.server.power.stats;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.LongArrayMultiStateCounter;
import com.android.internal.util.Preconditions;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
index 0facb9c..1637022 100644
--- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
@@ -21,7 +21,6 @@
import android.util.IndentingPrintWriter;
import android.util.SparseArray;
-import com.android.internal.os.MultiStateStats;
import com.android.internal.os.PowerStats;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
index 1f6f113..c267b79 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
@@ -22,7 +22,6 @@
import android.os.UidBatteryConsumer;
import android.util.Slog;
-import com.android.internal.os.MultiStateStats;
import com.android.internal.os.PowerStats;
import java.util.ArrayList;
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index e4c7fc1..6f27507 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -1851,14 +1851,14 @@
sessionState.currentChannel = channelUri;
notifyCurrentChannelInfosUpdatedLocked(userState);
if (!sessionState.isRecordingSession) {
- String actualInputId = getActualInputId(sessionState);
- if (!TextUtils.equals(mOnScreenInputId, actualInputId)) {
+ String sessionActualInputId = getSessionActualInputId(sessionState);
+ if (!TextUtils.equals(mOnScreenInputId, sessionActualInputId)) {
logExternalInputEvent(
FrameworkStatsLog
.EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__TUNED,
- actualInputId, sessionState);
+ sessionActualInputId, sessionState);
}
- mOnScreenInputId = actualInputId;
+ mOnScreenInputId = sessionActualInputId;
mOnScreenSessionState = sessionState;
}
}
@@ -2985,11 +2985,20 @@
// e.g. if an HDMI port has a CEC device plugged in, the actual input id of the HDMI
// session should be the input id of CEC device instead of the default HDMI input id.
@GuardedBy("mLock")
- private String getActualInputId(SessionState sessionState) {
+ private String getSessionActualInputId(SessionState sessionState) {
UserState userState = getOrCreateUserStateLocked(sessionState.userId);
TvInputState tvInputState = userState.inputMap.get(sessionState.inputId);
+ if (tvInputState == null) {
+ Slog.w(TAG, "No TvInputState for sessionState.inputId " + sessionState.inputId);
+ return sessionState.inputId;
+ }
TvInputInfo tvInputInfo = tvInputState.info;
- String actualInputId = sessionState.inputId;
+ if (tvInputInfo == null) {
+ Slog.w(TAG, "TvInputInfo is null for input id " + sessionState.inputId);
+ return sessionState.inputId;
+ }
+
+ String sessionActualInputId = sessionState.inputId;
switch (tvInputInfo.getType()) {
case TvInputInfo.TYPE_HDMI:
// TODO: find a better approach towards active CEC device in future
@@ -2997,13 +3006,13 @@
mTvInputHardwareManager.getHdmiParentInputMap();
if (hdmiParentInputMap.containsKey(sessionState.inputId)) {
List<String> parentInputList = hdmiParentInputMap.get(sessionState.inputId);
- actualInputId = parentInputList.get(0);
+ sessionActualInputId = parentInputList.get(0);
}
break;
default:
break;
}
- return actualInputId;
+ return sessionActualInputId;
}
@Nullable
@@ -3111,7 +3120,21 @@
private void logExternalInputEvent(int eventType, String inputId, SessionState sessionState) {
UserState userState = getOrCreateUserStateLocked(sessionState.userId);
TvInputState tvInputState = userState.inputMap.get(inputId);
+ if (tvInputState == null) {
+ Slog.w(TAG, "Cannot find input state for input id " + inputId);
+ // If input id is not found, try to find the input id of this sessionState.
+ inputId = sessionState.inputId;
+ tvInputState = userState.inputMap.get(inputId);
+ }
+ if (tvInputState == null) {
+ Slog.w(TAG, "Cannot find input state for sessionState.inputId " + inputId);
+ return;
+ }
TvInputInfo tvInputInfo = tvInputState.info;
+ if (tvInputInfo == null) {
+ Slog.w(TAG, "TvInputInfo is null for input id " + inputId);
+ return;
+ }
int inputState = tvInputState.state;
int inputType = tvInputInfo.getType();
String displayName = tvInputInfo.loadLabel(mContext).toString();
@@ -3647,14 +3670,14 @@
mSessionState.currentChannel = channelUri;
notifyCurrentChannelInfosUpdatedLocked(userState);
if (!mSessionState.isRecordingSession) {
- String actualInputId = getActualInputId(mSessionState);
- if (!TextUtils.equals(mOnScreenInputId, actualInputId)) {
+ String sessionActualInputId = getSessionActualInputId(mSessionState);
+ if (!TextUtils.equals(mOnScreenInputId, sessionActualInputId)) {
logExternalInputEvent(
FrameworkStatsLog
.EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__TUNED,
- actualInputId, mSessionState);
+ sessionActualInputId, mSessionState);
}
- mOnScreenInputId = actualInputId;
+ mOnScreenInputId = sessionActualInputId;
mOnScreenSessionState = mSessionState;
}
}
diff --git a/services/core/java/com/android/server/utils/AnrTimer.java b/services/core/java/com/android/server/utils/AnrTimer.java
index 2b6dffb..7b5192c 100644
--- a/services/core/java/com/android/server/utils/AnrTimer.java
+++ b/services/core/java/com/android/server/utils/AnrTimer.java
@@ -16,37 +16,22 @@
package com.android.server.utils;
-import static android.text.TextUtils.formatSimple;
-
-import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.os.Handler;
-import android.os.Looper;
import android.os.Message;
-import android.os.Process;
import android.os.SystemClock;
import android.os.Trace;
-import android.text.TextUtils;
import android.text.format.TimeMigrationUtils;
-import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
import android.util.Log;
-import android.util.MathUtils;
-import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.Keep;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.ProcessCpuTracker;
import com.android.internal.util.RingBuffer;
import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
-import java.util.concurrent.atomic.AtomicInteger;
/**
* This class managers AnrTimers. An AnrTimer is a substitute for a delayed Message. In legacy
@@ -102,12 +87,6 @@
private static final long TRACE_TAG = Trace.TRACE_TAG_ACTIVITY_MANAGER;
/**
- * Enable tracing from the time a timer expires until it is accepted or discarded. This is
- * used to diagnose long latencies in the client.
- */
- private static final boolean ENABLE_TRACING = false;
-
- /**
* Return true if the feature is enabled. By default, the value is take from the Flags class
* but it can be changed for local testing.
*/
@@ -116,22 +95,13 @@
}
/**
- * The status of an ANR timer. TIMER_INVALID status is returned when an error is detected.
+ * This class allows test code to provide instance-specific overrides.
*/
- private static final int TIMER_INVALID = 0;
- private static final int TIMER_RUNNING = 1;
- private static final int TIMER_EXPIRED = 2;
-
- @IntDef(prefix = { "TIMER_" }, value = {
- TIMER_INVALID, TIMER_RUNNING, TIMER_EXPIRED
- })
- private @interface TimerStatus {}
-
- /**
- * A static list of all known AnrTimer instances, used for dumping and testing.
- */
- @GuardedBy("sAnrTimerList")
- private static final ArrayList<WeakReference<AnrTimer>> sAnrTimerList = new ArrayList<>();
+ static class Injector {
+ boolean anrTimerServiceEnabled() {
+ return AnrTimer.anrTimerServiceEnabled();
+ }
+ }
/**
* An error is defined by its issue, the operation that detected the error, the tag of the
@@ -161,6 +131,22 @@
this.arg = arg;
this.timestamp = SystemClock.elapsedRealtime();
}
+
+ /**
+ * Dump a single error to the output stream.
+ */
+ private void dump(IndentingPrintWriter ipw, int seq) {
+ ipw.format("%2d: op:%s tag:%s issue:%s arg:%s\n", seq, operation, tag, issue, arg);
+
+ final long offset = System.currentTimeMillis() - SystemClock.elapsedRealtime();
+ final long etime = offset + timestamp;
+ ipw.println(" date:" + TimeMigrationUtils.formatMillisWithFixedFormat(etime));
+ ipw.increaseIndent();
+ for (int i = 0; i < stack.length; i++) {
+ ipw.println(" " + stack[i].toString());
+ }
+ ipw.decreaseIndent();
+ }
}
/**
@@ -171,132 +157,10 @@
@GuardedBy("sErrors")
private static final RingBuffer<Error> sErrors = new RingBuffer<>(Error.class, 20);
- /**
- * A record of a single anr timer. The pid and uid are retained for reference but they do not
- * participate in the equality tests. A {@link Timer} is bound to its parent {@link AnrTimer}
- * through the owner field. Access to timer fields is guarded by the mLock of the owner.
- */
- private static class Timer {
- /** The AnrTimer that is managing this Timer. */
- final AnrTimer owner;
-
- /** The argument that uniquely identifies the Timer in the context of its current owner. */
- final Object arg;
- /** The pid of the process being tracked by this Timer. */
- final int pid;
- /** The uid of the process being tracked by this Timer as reported by the kernel. */
- final int uid;
- /** The original timeout. */
- final long timeoutMs;
-
- /** The status of the Timer. */
- @GuardedBy("owner.mLock")
- @TimerStatus
- int status;
-
- /** The absolute time the timer was startd */
- final long startedMs;
-
- /** Fields used by the native timer service. */
-
- /** The timer ID: used to exchange information with the native service. */
- int timerId;
-
- /** Fields used by the legacy timer service. */
-
- /**
- * The process's cpu delay time when the timer starts . It is meaningful only if
- * extendable is true. The cpu delay is cumulative, so the incremental delay that occurs
- * during a timer is the delay at the end of the timer minus this value. Units are in
- * milliseconds.
- */
- @GuardedBy("owner.mLock")
- long initialCpuDelayMs;
-
- /** True if the timer has been extended. */
- @GuardedBy("owner.mLock")
- boolean extended;
-
- /**
- * Fetch a new Timer. This is private. Clients should get a new timer using the obtain()
- * method.
- */
- private Timer(int pid, int uid, @Nullable Object arg, long timeoutMs,
- @NonNull AnrTimer service) {
- this.arg = arg;
- this.pid = pid;
- this.uid = uid;
- this.timerId = 0;
- this.timeoutMs = timeoutMs;
- this.startedMs = now();
- this.owner = service;
- this.initialCpuDelayMs = 0;
- this.extended = false;
- this.status = TIMER_INVALID;
- }
-
- /** Get a timer. This implementation constructs a new timer. */
- static Timer obtain(int pid, int uid, @Nullable Object arg, long timeout,
- @NonNull AnrTimer service) {
- return new Timer(pid, uid, arg, timeout, service);
- }
-
- /** Release a timer. This implementation simply drops the timer. */
- void release() {
- }
-
- /** Return the age of the timer. This is used for debugging. */
- long age() {
- return now() - startedMs;
- }
-
- /**
- * The hash code is generated from the owner and the argument. By definition, the
- * combination must be unique for the lifetime of an in-use Timer.
- */
- @Override
- public int hashCode() {
- return Objects.hash(owner, arg);
- }
-
- /**
- * The equality check compares the owner and the argument. By definition, the combination
- * must be unique for the lifetime of an in-use Timer.
- */
- @Override
- public boolean equals(Object r) {
- if (r instanceof Timer) {
- Timer t = (Timer) r;
- return Objects.equals(owner, t.owner) && Objects.equals(arg, t.arg);
- }
- return false;
- }
-
- @Override
- public String toString() {
- final int myStatus;
- synchronized (owner.mLock) {
- myStatus = status;
- }
- return "timerId=" + timerId + " pid=" + pid + " uid=" + uid
- + " " + statusString(myStatus) + " " + owner.mLabel;
- }
- }
-
/** A lock for the AnrTimer instance. */
private final Object mLock = new Object();
/**
- * The map from client argument to the associated timer.
- */
- @GuardedBy("mLock")
- private final ArrayMap<V, Timer> mTimerMap = new ArrayMap<>();
-
- /** The highwater mark of started, but not closed, timers. */
- @GuardedBy("mLock")
- private int mMaxStarted = 0;
-
- /**
* The total number of timers started.
*/
@GuardedBy("mLock")
@@ -309,176 +173,6 @@
private int mTotalErrors = 0;
/**
- * The total number of timers that have expired.
- */
- @GuardedBy("mLock")
- private int mTotalExpired = 0;
-
- /**
- * A TimerService that generates a timeout event <n> milliseconds in the future. See the
- * class documentation for an explanation of the operations.
- */
- private abstract class TimerService {
- /** Start a timer. The timeout must be initialized. */
- abstract boolean start(@NonNull Timer timer);
-
- abstract void cancel(@NonNull Timer timer);
-
- abstract void accept(@NonNull Timer timer);
-
- abstract void discard(@NonNull Timer timer);
- }
-
- /**
- * A class to assist testing. All methods are null by default but can be overridden as
- * necessary for a test.
- */
- @VisibleForTesting
- static class Injector {
- private final Handler mReferenceHandler;
-
- Injector(@NonNull Handler handler) {
- mReferenceHandler = handler;
- }
-
- /**
- * Return a handler for the given Callback, based on the reference handler. The handler
- * might be mocked, in which case it does not have a valid Looper. In this case, use the
- * main Looper.
- */
- @NonNull
- Handler newHandler(@NonNull Handler.Callback callback) {
- Looper looper = mReferenceHandler.getLooper();
- if (looper == null) looper = Looper.getMainLooper();
- return new Handler(looper, callback);
- }
-
- /**
- * Return a CpuTracker. The default behavior is to create a new CpuTracker but this changes
- * for unit tests.
- **/
- @NonNull
- CpuTracker newTracker() {
- return new CpuTracker();
- }
-
- /** Return true if the feature is enabled. */
- boolean isFeatureEnabled() {
- return anrTimerServiceEnabled();
- }
- }
-
- /**
- * A helper class to measure CPU delays. Given a process ID, this class will return the
- * cumulative CPU delay for the PID, since process inception. This class is defined to assist
- * testing.
- */
- @VisibleForTesting
- static class CpuTracker {
- /**
- * The parameter to ProcessCpuTracker indicates that statistics should be collected on a
- * single process and not on the collection of threads associated with that process.
- */
- private final ProcessCpuTracker mCpu = new ProcessCpuTracker(false);
-
- /** A simple wrapper to fetch the delay. This method can be overridden for testing. */
- long delay(int pid) {
- return mCpu.getCpuDelayTimeForPid(pid);
- }
- }
-
- /**
- * The "user-space" implementation of the timer service. This service uses its own message
- * handler to create timeouts.
- */
- private class HandlerTimerService extends TimerService {
- /** The lock for this handler */
- private final Object mLock = new Object();
-
- /** The message handler for scheduling future events. */
- private final Handler mHandler;
-
- /** The interface to fetch process statistics that might extend an ANR timeout. */
- private final CpuTracker mCpu;
-
- /** Create a HandlerTimerService that directly uses the supplied handler and tracker. */
- @VisibleForTesting
- HandlerTimerService(@NonNull Injector injector) {
- mHandler = injector.newHandler(this::expires);
- mCpu = injector.newTracker();
- }
-
- /** Post a message with the specified timeout. The timer is not modified. */
- private void post(@NonNull Timer t, long timeoutMillis) {
- final Message msg = mHandler.obtainMessage();
- msg.obj = t;
- mHandler.sendMessageDelayed(msg, timeoutMillis);
- }
-
- /**
- * The local expiration handler first attempts to compute a timer extension. If the timer
- * should be extended, it is rescheduled in the future (granting more time to the
- * associated process). If the timer should not be extended then the timeout is delivered
- * to the client.
- *
- * A process is extended to account for the time the process was swapped out and was not
- * runnable through no fault of its own. A timer can only be extended once and only if
- * the AnrTimer permits extensions. Finally, a timer will never be extended by more than
- * the original timeout, so the total timeout will never be more than twice the originally
- * configured timeout.
- */
- private boolean expires(Message msg) {
- Timer t = (Timer) msg.obj;
- synchronized (mLock) {
- long extension = 0;
- if (mExtend && !t.extended) {
- extension = mCpu.delay(t.pid) - t.initialCpuDelayMs;
- if (extension < 0) extension = 0;
- if (extension > t.timeoutMs) extension = t.timeoutMs;
- t.extended = true;
- }
- if (extension > 0) {
- post(t, extension);
- } else {
- onExpiredLocked(t);
- }
- }
- return true;
- }
-
- @GuardedBy("mLock")
- @Override
- boolean start(@NonNull Timer t) {
- if (mExtend) {
- t.initialCpuDelayMs = mCpu.delay(t.pid);
- }
- post(t, t.timeoutMs);
- return true;
- }
-
- @Override
- void cancel(@NonNull Timer t) {
- mHandler.removeMessages(0, t);
- }
-
- @Override
- void accept(@NonNull Timer t) {
- // Nothing to do.
- }
-
- @Override
- void discard(@NonNull Timer t) {
- // Nothing to do.
- }
-
- /** The string identifies this subclass of AnrTimerService as being based on handlers. */
- @Override
- public String toString() {
- return "handler";
- }
- }
-
- /**
* The handler for messages sent from this instance.
*/
private final Handler mHandler;
@@ -499,18 +193,6 @@
private final boolean mExtend;
/**
- * The timer service to use for this AnrTimer.
- */
- private final TimerService mTimerService;
-
- /**
- * Whether or not canceling a non-existent timer is an error. Clients often cancel freely
- * preemptively, without knowing if the timer was ever started. Keeping this variable true
- * means that such behavior is not an error.
- */
- private final boolean mLenientCancel = true;
-
- /**
* The top-level switch for the feature enabled or disabled.
*/
private final FeatureSwitch mFeature;
@@ -528,40 +210,34 @@
* AnrTimer may extend the individual timer rather than immediately delivering the timeout to
* the client. The extension policy is not part of the instance.
*
- * This method accepts an {@link #Injector} to tune behavior for testing. This method should
- * not be called directly by regular clients.
- *
* @param handler The handler to which the expiration message will be delivered.
* @param what The "what" parameter for the expiration message.
* @param label A name for this instance.
* @param extend A flag to indicate if expired timers can be granted extensions.
- * @param injector An {@link #Injector} to tune behavior for testing.
+ * @param injector An injector to provide overrides for testing.
*/
@VisibleForTesting
AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend,
- @NonNull Injector injector) {
+ @NonNull Injector injector) {
mHandler = handler;
mWhat = what;
mLabel = label;
mExtend = extend;
- boolean enabled = injector.isFeatureEnabled();
- if (!enabled) {
- mFeature = new FeatureDisabled();
- mTimerService = null;
- } else {
- mFeature = new FeatureEnabled();
- mTimerService = new HandlerTimerService(injector);
-
- synchronized (sAnrTimerList) {
- sAnrTimerList.add(new WeakReference(this));
- }
- }
- Log.i(TAG, formatSimple("created %s label: \"%s\"", mTimerService, label));
+ mFeature = new FeatureDisabled();
}
/**
- * Create an AnrTimer instance with the default {@link #Injector}. See {@link AnrTimer(Handler,
- * int, String, boolean, Injector} for a functional description.
+ * Create one AnrTimer instance. The instance is given a handler and a "what". Individual
+ * timers are started with {@link #start}. If a timer expires, then a {@link Message} is sent
+ * immediately to the handler with {@link Message.what} set to what and {@link Message.obj} set
+ * to the timer key.
+ *
+ * AnrTimer instances have a label, which must be unique. The label is used for reporting and
+ * debug.
+ *
+ * If an individual timer expires internally, and the "extend" parameter is true, then the
+ * AnrTimer may extend the individual timer rather than immediately delivering the timeout to
+ * the client. The extension policy is not part of the instance.
*
* @param handler The handler to which the expiration message will be delivered.
* @param what The "what" parameter for the expiration message.
@@ -569,7 +245,7 @@
* @param extend A flag to indicate if expired timers can be granted extensions.
*/
public AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend) {
- this(handler, what, label, extend, new Injector(handler));
+ this(handler, what, label, extend, new Injector());
}
/**
@@ -596,105 +272,17 @@
}
/**
- * Start a trace on the timer. The trace is laid down in the AnrTimerTrack.
- */
- private void traceBegin(Timer t, String what) {
- if (ENABLE_TRACING) {
- final String label = formatSimple("%s(%d,%d,%s)", what, t.pid, t.uid, mLabel);
- final int cookie = t.hashCode();
- Trace.asyncTraceForTrackBegin(TRACE_TAG, TRACK, label, cookie);
- }
- }
-
- /**
- * End a trace on the timer.
- */
- private void traceEnd(Timer t) {
- if (ENABLE_TRACING) {
- final int cookie = t.hashCode();
- Trace.asyncTraceForTrackEnd(TRACE_TAG, TRACK, cookie);
- }
- }
-
- /**
- * Return the string representation for a timer status.
- */
- private static String statusString(int s) {
- switch (s) {
- case TIMER_INVALID: return "invalid";
- case TIMER_RUNNING: return "running";
- case TIMER_EXPIRED: return "expired";
- }
- return formatSimple("unknown: %d", s);
- }
-
- /**
- * Delete the timer associated with arg from the maps and return it. Return null if the timer
- * was not found.
- */
- @GuardedBy("mLock")
- private Timer removeLocked(V arg) {
- Timer timer = mTimerMap.remove(arg);
- return timer;
- }
-
- /**
- * Return the number of timers currently running.
- */
- @VisibleForTesting
- static int sizeOfTimerList() {
- synchronized (sAnrTimerList) {
- int totalTimers = 0;
- for (int i = 0; i < sAnrTimerList.size(); i++) {
- AnrTimer client = sAnrTimerList.get(i).get();
- if (client != null) totalTimers += client.mTimerMap.size();
- }
- return totalTimers;
- }
- }
-
- /**
- * Clear out all existing timers. This will lead to unexpected behavior if used carelessly.
- * It is available only for testing. It returns the number of times that were actually
- * erased.
- */
- @VisibleForTesting
- static int resetTimerListForHermeticTest() {
- synchronized (sAnrTimerList) {
- int mapLen = 0;
- for (int i = 0; i < sAnrTimerList.size(); i++) {
- AnrTimer client = sAnrTimerList.get(i).get();
- if (client != null) {
- mapLen += client.mTimerMap.size();
- client.mTimerMap.clear();
- }
- }
- if (mapLen > 0) {
- Log.w(TAG, formatSimple("erasing timer list: clearing %d timers", mapLen));
- }
- return mapLen;
- }
- }
-
- /**
- * Generate a log message for a timer.
- */
- private void report(@NonNull Timer timer, @NonNull String msg) {
- Log.i(TAG, msg + " " + timer + " " + Objects.toString(timer.arg));
- }
-
- /**
* The FeatureSwitch class provides a quick switch between feature-enabled behavior and
* feature-disabled behavior.
*/
private abstract class FeatureSwitch {
- abstract boolean start(@NonNull V arg, int pid, int uid, long timeoutMs);
+ abstract void start(@NonNull V arg, int pid, int uid, long timeoutMs);
- abstract boolean cancel(@NonNull V arg);
+ abstract void cancel(@NonNull V arg);
- abstract boolean accept(@NonNull V arg);
+ abstract void accept(@NonNull V arg);
- abstract boolean discard(@NonNull V arg);
+ abstract void discard(@NonNull V arg);
abstract boolean enabled();
}
@@ -706,29 +294,25 @@
private class FeatureDisabled extends FeatureSwitch {
/** Start a timer by sending a message to the client's handler. */
@Override
- boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
+ void start(@NonNull V arg, int pid, int uid, long timeoutMs) {
final Message msg = mHandler.obtainMessage(mWhat, arg);
mHandler.sendMessageDelayed(msg, timeoutMs);
- return true;
}
/** Cancel a timer by removing the message from the client's handler. */
@Override
- boolean cancel(@NonNull V arg) {
+ void cancel(@NonNull V arg) {
mHandler.removeMessages(mWhat, arg);
- return true;
}
/** accept() is a no-op when the feature is disabled. */
@Override
- boolean accept(@NonNull V arg) {
- return true;
+ void accept(@NonNull V arg) {
}
/** discard() is a no-op when the feature is disabled. */
@Override
- boolean discard(@NonNull V arg) {
- return true;
+ void discard(@NonNull V arg) {
}
/** The feature is not enabled. */
@@ -739,113 +323,6 @@
}
/**
- * The FeatureEnabled class enables the AnrTimer logic. It is used when the AnrTimer service
- * is enabled via Flags.anrTimerServiceEnabled.
- */
- private class FeatureEnabled extends FeatureSwitch {
-
- /**
- * Start a timer.
- */
- @Override
- boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
- final Timer timer = Timer.obtain(pid, uid, arg, timeoutMs, AnrTimer.this);
- synchronized (mLock) {
- Timer old = mTimerMap.get(arg);
- // There is an existing timer. If the timer was running, then cancel the running
- // timer and restart it. If the timer was expired record a protocol error and
- // discard the expired timer.
- if (old != null) {
- if (old.status == TIMER_EXPIRED) {
- restartedLocked(old.status, arg);
- discard(arg);
- } else {
- cancel(arg);
- }
- }
- if (mTimerService.start(timer)) {
- timer.status = TIMER_RUNNING;
- mTimerMap.put(arg, timer);
- mTotalStarted++;
- mMaxStarted = Math.max(mMaxStarted, mTimerMap.size());
- if (DEBUG) report(timer, "start");
- return true;
- } else {
- Log.e(TAG, "AnrTimer.start failed");
- return false;
- }
- }
- }
-
- /**
- * Cancel a timer. Return false if the timer was not found.
- */
- @Override
- boolean cancel(@NonNull V arg) {
- synchronized (mLock) {
- Timer timer = removeLocked(arg);
- if (timer == null) {
- if (!mLenientCancel) notFoundLocked("cancel", arg);
- return false;
- }
- mTimerService.cancel(timer);
- // There may be an expiration message in flight. Cancel it.
- mHandler.removeMessages(mWhat, arg);
- if (DEBUG) report(timer, "cancel");
- timer.release();
- return true;
- }
- }
-
- /**
- * Accept a timer in the framework-level handler. The timeout has been accepted and the
- * timeout handler is executing. Return false if the timer was not found.
- */
- @Override
- boolean accept(@NonNull V arg) {
- synchronized (mLock) {
- Timer timer = removeLocked(arg);
- if (timer == null) {
- notFoundLocked("accept", arg);
- return false;
- }
- mTimerService.accept(timer);
- traceEnd(timer);
- if (DEBUG) report(timer, "accept");
- timer.release();
- return true;
- }
- }
-
- /**
- * Discard a timer in the framework-level handler. For whatever reason, the timer is no
- * longer interesting. No statistics are collected. Return false if the time was not
- * found.
- */
- @Override
- boolean discard(@NonNull V arg) {
- synchronized (mLock) {
- Timer timer = removeLocked(arg);
- if (timer == null) {
- notFoundLocked("discard", arg);
- return false;
- }
- mTimerService.discard(timer);
- traceEnd(timer);
- if (DEBUG) report(timer, "discard");
- timer.release();
- return true;
- }
- }
-
- /** The feature is enabled. */
- @Override
- boolean enabled() {
- return true;
- }
- }
-
- /**
* Start a timer associated with arg. The same object must be used to cancel, accept, or
* discard a timer later. If a timer already exists with the same arg, then the existing timer
* is canceled and a new timer is created.
@@ -854,32 +331,27 @@
* @param pid The Linux process ID of the target being timed.
* @param uid The Linux user ID of the target being timed.
* @param timeoutMs The timer timeout, in milliseconds.
- * @return true if the timer was successfully created.
*/
- public boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
- return mFeature.start(arg, pid, uid, timeoutMs);
+ public void start(@NonNull V arg, int pid, int uid, long timeoutMs) {
+ mFeature.start(arg, pid, uid, timeoutMs);
}
/**
* Cancel the running timer associated with arg. The timer is forgotten. If the timer has
* expired, the call is treated as a discard. No errors are reported if the timer does not
* exist or if the timer has expired.
- *
- * @return true if the timer was found and was running.
*/
- public boolean cancel(@NonNull V arg) {
- return mFeature.cancel(arg);
+ public void cancel(@NonNull V arg) {
+ mFeature.cancel(arg);
}
/**
* Accept the expired timer associated with arg. This indicates that the caller considers the
* timer expiration to be a true ANR. (See {@link #discard} for an alternate response.) It is
* an error to accept a running timer, however the running timer will be canceled.
- *
- * @return true if the timer was found and was expired.
*/
- public boolean accept(@NonNull V arg) {
- return mFeature.accept(arg);
+ public void accept(@NonNull V arg) {
+ mFeature.accept(arg);
}
/**
@@ -889,24 +361,9 @@
* such a process could be stopped at a breakpoint and its failure to respond would not be an
* error. It is an error to discard a running timer, however the running timer will be
* canceled.
- *
- * @return true if the timer was found and was expired.
*/
- public boolean discard(@NonNull V arg) {
- return mFeature.discard(arg);
- }
-
- /**
- * The notifier that a timer has fired. The timer is not modified.
- */
- @GuardedBy("mLock")
- private void onExpiredLocked(@NonNull Timer timer) {
- if (DEBUG) report(timer, "expire");
- traceBegin(timer, "expired");
- mHandler.sendMessage(Message.obtain(mHandler, mWhat, timer.arg));
- synchronized (mLock) {
- mTotalExpired++;
- }
+ public void discard(@NonNull V arg) {
+ mFeature.discard(arg);
}
/**
@@ -916,9 +373,7 @@
synchronized (mLock) {
pw.format("timer: %s\n", mLabel);
pw.increaseIndent();
- pw.format("started=%d maxStarted=%d running=%d expired=%d error=%d\n",
- mTotalStarted, mMaxStarted, mTimerMap.size(),
- mTotalExpired, mTotalErrors);
+ pw.format("started=%d errors=%d\n", mTotalStarted, mTotalErrors);
pw.decreaseIndent();
}
}
@@ -931,10 +386,20 @@
}
/**
- * The current time in milliseconds.
+ * Dump all errors to the output stream.
*/
- private static long now() {
- return SystemClock.uptimeMillis();
+ private static void dumpErrors(IndentingPrintWriter ipw) {
+ Error errors[];
+ synchronized (sErrors) {
+ if (sErrors.size() == 0) return;
+ errors = sErrors.toArray();
+ }
+ ipw.println("Errors");
+ ipw.increaseIndent();
+ for (int i = 0; i < errors.length; i++) {
+ if (errors[i] != null) errors[i].dump(ipw, i);
+ }
+ ipw.decreaseIndent();
}
/**
@@ -966,60 +431,12 @@
}
/**
- * Log an error about a timer that is started when there is an existing timer.
- */
- @GuardedBy("mLock")
- private void restartedLocked(@TimerStatus int status, Object arg) {
- recordErrorLocked("start", status == TIMER_EXPIRED ? "autoDiscard" : "autoCancel", arg);
- }
-
- /**
- * Dump a single error to the output stream.
- */
- private static void dump(IndentingPrintWriter ipw, int seq, Error err) {
- ipw.format("%2d: op:%s tag:%s issue:%s arg:%s\n", seq, err.operation, err.tag,
- err.issue, err.arg);
-
- final long offset = System.currentTimeMillis() - SystemClock.elapsedRealtime();
- final long etime = offset + err.timestamp;
- ipw.println(" date:" + TimeMigrationUtils.formatMillisWithFixedFormat(etime));
- ipw.increaseIndent();
- for (int i = 0; i < err.stack.length; i++) {
- ipw.println(" " + err.stack[i].toString());
- }
- ipw.decreaseIndent();
- }
-
- /**
- * Dump all errors to the output stream.
- */
- private static void dumpErrors(IndentingPrintWriter ipw) {
- Error errors[];
- synchronized (sErrors) {
- if (sErrors.size() == 0) return;
- errors = sErrors.toArray();
- }
- ipw.println("Errors");
- ipw.increaseIndent();
- for (int i = 0; i < errors.length; i++) {
- if (errors[i] != null) dump(ipw, i, errors[i]);
- }
- ipw.decreaseIndent();
- }
-
- /**
* Dumpsys output.
*/
public static void dump(@NonNull PrintWriter pw, boolean verbose) {
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
ipw.println("AnrTimer statistics");
ipw.increaseIndent();
- synchronized (sAnrTimerList) {
- for (int i = 0; i < sAnrTimerList.size(); i++) {
- AnrTimer client = sAnrTimerList.get(i).get();
- if (client != null) client.dump(ipw);
- }
- }
if (verbose) dumpErrors(ipw);
ipw.format("AnrTimerEnd\n");
ipw.decreaseIndent();
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
new file mode 100644
index 0000000..975fdc0
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.WindowManager.TRANSIT_CHANGE;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS;
+import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
+import static com.android.server.wm.utils.DisplayInfoOverrides.WM_OVERRIDE_FIELDS;
+import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.view.DisplayInfo;
+import android.window.DisplayAreaInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.server.wm.utils.DisplayInfoOverrides.DisplayInfoFieldsUpdater;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * A DisplayUpdater that could defer and queue display updates coming from DisplayManager to
+ * WindowManager. It allows to defer pending display updates if WindowManager is currently not
+ * ready to apply them.
+ * For example, this might happen if there is a Shell transition running and physical display
+ * changed. We can't immediately apply the display updates because we want to start a separate
+ * display change transition. In this case, we will queue all display updates until the current
+ * transition's collection finishes and then apply them afterwards.
+ */
+public class DeferredDisplayUpdater implements DisplayUpdater {
+
+ /**
+ * List of fields that could be deferred before applying to DisplayContent.
+ * This should be kept in sync with {@link DeferredDisplayUpdater#calculateDisplayInfoDiff}
+ */
+ @VisibleForTesting
+ static final DisplayInfoFieldsUpdater DEFERRABLE_FIELDS = (out, override) -> {
+ // Treat unique id and address change as WM-specific display change as we re-query display
+ // settings and parameters based on it which could cause window changes
+ out.uniqueId = override.uniqueId;
+ out.address = override.address;
+
+ // Also apply WM-override fields, since they might produce differences in window hierarchy
+ WM_OVERRIDE_FIELDS.setFields(out, override);
+ };
+
+ private final DisplayContent mDisplayContent;
+
+ @NonNull
+ private final DisplayInfo mNonOverrideDisplayInfo = new DisplayInfo();
+
+ /**
+ * The last known display parameters from DisplayManager, some WM-specific fields in this object
+ * might not be applied to the DisplayContent yet
+ */
+ @Nullable
+ private DisplayInfo mLastDisplayInfo;
+
+ /**
+ * The last DisplayInfo that was applied to DisplayContent, only WM-specific parameters must be
+ * used from this object. This object is used to store old values of DisplayInfo while these
+ * fields are pending to be applied to DisplayContent.
+ */
+ @Nullable
+ private DisplayInfo mLastWmDisplayInfo;
+
+ @NonNull
+ private final DisplayInfo mOutputDisplayInfo = new DisplayInfo();
+
+ public DeferredDisplayUpdater(@NonNull DisplayContent displayContent) {
+ mDisplayContent = displayContent;
+ mNonOverrideDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo());
+ }
+
+ /**
+ * Reads the latest display parameters from the display manager and returns them in a callback.
+ * If there are pending display updates, it will wait for them to finish first and only then it
+ * will call the callback with the latest display parameters.
+ *
+ * @param finishCallback is called when all pending display updates are finished
+ */
+ @Override
+ public void updateDisplayInfo(@NonNull Runnable finishCallback) {
+ // Get the latest display parameters from the DisplayManager
+ final DisplayInfo displayInfo = getCurrentDisplayInfo();
+
+ final int displayInfoDiff = calculateDisplayInfoDiff(mLastDisplayInfo, displayInfo);
+ final boolean physicalDisplayUpdated = isPhysicalDisplayUpdated(mLastDisplayInfo,
+ displayInfo);
+
+ mLastDisplayInfo = displayInfo;
+
+ // Apply whole display info immediately as is if either:
+ // * it is the first display update
+ // * shell transitions are disabled or temporary unavailable
+ if (displayInfoDiff == DIFF_EVERYTHING
+ || !mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
+ ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
+ "DeferredDisplayUpdater: applying DisplayInfo immediately");
+
+ mLastWmDisplayInfo = displayInfo;
+ applyLatestDisplayInfo();
+ finishCallback.run();
+ return;
+ }
+
+ // If there are non WM-specific display info changes, apply only these fields immediately
+ if ((displayInfoDiff & DIFF_NOT_WM_DEFERRABLE) > 0) {
+ ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
+ "DeferredDisplayUpdater: partially applying DisplayInfo immediately");
+ applyLatestDisplayInfo();
+ }
+
+ // If there are WM-specific display info changes, apply them through a Shell transition
+ if ((displayInfoDiff & DIFF_WM_DEFERRABLE) > 0) {
+ ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
+ "DeferredDisplayUpdater: deferring DisplayInfo update");
+
+ requestDisplayChangeTransition(physicalDisplayUpdated, () -> {
+ // Apply deferrable fields to DisplayContent only when the transition
+ // starts collecting, non-deferrable fields are ignored in mLastWmDisplayInfo
+ mLastWmDisplayInfo = displayInfo;
+ applyLatestDisplayInfo();
+ finishCallback.run();
+ });
+ } else {
+ // There are no WM-specific updates, so we can immediately notify that all display
+ // info changes are applied
+ finishCallback.run();
+ }
+ }
+
+ /**
+ * Requests a display change Shell transition
+ *
+ * @param physicalDisplayUpdated if true also starts remote display change
+ * @param onStartCollect called when the Shell transition starts collecting
+ */
+ private void requestDisplayChangeTransition(boolean physicalDisplayUpdated,
+ @NonNull Runnable onStartCollect) {
+
+ final Transition transition = new Transition(TRANSIT_CHANGE, /* flags= */ 0,
+ mDisplayContent.mTransitionController,
+ mDisplayContent.mTransitionController.mSyncEngine);
+
+ mDisplayContent.mAtmService.startPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
+
+ mDisplayContent.mTransitionController.startCollectOrQueue(transition, deferred -> {
+ final Rect startBounds = new Rect(0, 0, mDisplayContent.mInitialDisplayWidth,
+ mDisplayContent.mInitialDisplayHeight);
+ final int fromRotation = mDisplayContent.getRotation();
+
+ onStartCollect.run();
+
+ ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
+ "DeferredDisplayUpdater: applied DisplayInfo after deferring");
+
+ if (physicalDisplayUpdated) {
+ onDisplayUpdated(transition, fromRotation, startBounds);
+ } else {
+ transition.setAllReady();
+ }
+ });
+ }
+
+ /**
+ * Applies current DisplayInfo to DisplayContent, DisplayContent is merged from two parts:
+ * - non-deferrable fields are set from the most recent values received from DisplayManager
+ * (uses {@link mLastDisplayInfo} field)
+ * - deferrable fields are set from the latest values that we could apply to WM
+ * (uses {@link mLastWmDisplayInfo} field)
+ */
+ private void applyLatestDisplayInfo() {
+ copyDisplayInfoFields(mOutputDisplayInfo, /* base= */ mLastDisplayInfo,
+ /* override= */ mLastWmDisplayInfo, /* fields= */ DEFERRABLE_FIELDS);
+ mDisplayContent.onDisplayInfoUpdated(mOutputDisplayInfo);
+ }
+
+ @NonNull
+ private DisplayInfo getCurrentDisplayInfo() {
+ mDisplayContent.mWmService.mDisplayManagerInternal.getNonOverrideDisplayInfo(
+ mDisplayContent.mDisplayId, mNonOverrideDisplayInfo);
+ return new DisplayInfo(mNonOverrideDisplayInfo);
+ }
+
+ /**
+ * Called when physical display is updated, this could happen e.g. on foldable
+ * devices when the physical underlying display is replaced. This method should be called
+ * when the new display info is already applied to the WM hierarchy.
+ *
+ * @param fromRotation rotation before the display change
+ * @param startBounds display bounds before the display change
+ */
+ private void onDisplayUpdated(@NonNull Transition transition, int fromRotation,
+ @NonNull Rect startBounds) {
+ final Rect endBounds = new Rect(0, 0, mDisplayContent.mInitialDisplayWidth,
+ mDisplayContent.mInitialDisplayHeight);
+ final int toRotation = mDisplayContent.getRotation();
+
+ final TransitionRequestInfo.DisplayChange displayChange =
+ new TransitionRequestInfo.DisplayChange(mDisplayContent.getDisplayId());
+ displayChange.setStartAbsBounds(startBounds);
+ displayChange.setEndAbsBounds(endBounds);
+ displayChange.setStartRotation(fromRotation);
+ displayChange.setEndRotation(toRotation);
+ displayChange.setPhysicalDisplayChanged(true);
+
+ mDisplayContent.mTransitionController.requestStartTransition(transition,
+ /* startTask= */ null, /* remoteTransition= */ null, displayChange);
+
+ final DisplayAreaInfo newDisplayAreaInfo = mDisplayContent.getDisplayAreaInfo();
+
+ final boolean startedRemoteChange = mDisplayContent.mRemoteDisplayChangeController
+ .performRemoteDisplayChange(fromRotation, toRotation, newDisplayAreaInfo,
+ transaction -> finishDisplayUpdate(transaction, transition));
+
+ if (!startedRemoteChange) {
+ finishDisplayUpdate(/* wct= */ null, transition);
+ }
+ }
+
+ private void finishDisplayUpdate(@Nullable WindowContainerTransaction wct,
+ @NonNull Transition transition) {
+ if (wct != null) {
+ mDisplayContent.mAtmService.mWindowOrganizerController.applyTransaction(
+ wct);
+ }
+ transition.setAllReady();
+ }
+
+ private boolean isPhysicalDisplayUpdated(@Nullable DisplayInfo first,
+ @Nullable DisplayInfo second) {
+ if (first == null || second == null) return true;
+ return !Objects.equals(first.uniqueId, second.uniqueId);
+ }
+
+ /**
+ * Diff result: fields are the same
+ */
+ static final int DIFF_NONE = 0;
+
+ /**
+ * Diff result: fields that could be deferred in WM are different
+ */
+ static final int DIFF_WM_DEFERRABLE = 1 << 0;
+
+ /**
+ * Diff result: fields that could not be deferred in WM are different
+ */
+ static final int DIFF_NOT_WM_DEFERRABLE = 1 << 1;
+
+ /**
+ * Diff result: everything is different
+ */
+ static final int DIFF_EVERYTHING = 0XFFFFFFFF;
+
+ @VisibleForTesting
+ static int calculateDisplayInfoDiff(@Nullable DisplayInfo first, @Nullable DisplayInfo second) {
+ int diff = DIFF_NONE;
+
+ if (Objects.equals(first, second)) return diff;
+ if (first == null || second == null) return DIFF_EVERYTHING;
+
+ if (first.layerStack != second.layerStack
+ || first.flags != second.flags
+ || first.type != second.type
+ || first.displayId != second.displayId
+ || first.displayGroupId != second.displayGroupId
+ || !Objects.equals(first.deviceProductInfo, second.deviceProductInfo)
+ || first.modeId != second.modeId
+ || first.renderFrameRate != second.renderFrameRate
+ || first.defaultModeId != second.defaultModeId
+ || first.userPreferredModeId != second.userPreferredModeId
+ || !Arrays.equals(first.supportedModes, second.supportedModes)
+ || first.colorMode != second.colorMode
+ || !Arrays.equals(first.supportedColorModes, second.supportedColorModes)
+ || !Objects.equals(first.hdrCapabilities, second.hdrCapabilities)
+ || !Arrays.equals(first.userDisabledHdrTypes, second.userDisabledHdrTypes)
+ || first.minimalPostProcessingSupported != second.minimalPostProcessingSupported
+ || first.appVsyncOffsetNanos != second.appVsyncOffsetNanos
+ || first.presentationDeadlineNanos != second.presentationDeadlineNanos
+ || first.state != second.state
+ || first.committedState != second.committedState
+ || first.ownerUid != second.ownerUid
+ || !Objects.equals(first.ownerPackageName, second.ownerPackageName)
+ || first.removeMode != second.removeMode
+ || first.getRefreshRate() != second.getRefreshRate()
+ || first.brightnessMinimum != second.brightnessMinimum
+ || first.brightnessMaximum != second.brightnessMaximum
+ || first.brightnessDefault != second.brightnessDefault
+ || first.installOrientation != second.installOrientation
+ || !Objects.equals(first.layoutLimitedRefreshRate, second.layoutLimitedRefreshRate)
+ || !BrightnessSynchronizer.floatEquals(first.hdrSdrRatio, second.hdrSdrRatio)
+ || !first.thermalRefreshRateThrottling.contentEquals(
+ second.thermalRefreshRateThrottling)
+ || !Objects.equals(first.thermalBrightnessThrottlingDataId,
+ second.thermalBrightnessThrottlingDataId)) {
+ diff |= DIFF_NOT_WM_DEFERRABLE;
+ }
+
+ if (first.appWidth != second.appWidth
+ || first.appHeight != second.appHeight
+ || first.smallestNominalAppWidth != second.smallestNominalAppWidth
+ || first.smallestNominalAppHeight != second.smallestNominalAppHeight
+ || first.largestNominalAppWidth != second.largestNominalAppWidth
+ || first.largestNominalAppHeight != second.largestNominalAppHeight
+ || first.logicalWidth != second.logicalWidth
+ || first.logicalHeight != second.logicalHeight
+ || first.physicalXDpi != second.physicalXDpi
+ || first.physicalYDpi != second.physicalYDpi
+ || first.rotation != second.rotation
+ || !Objects.equals(first.displayCutout, second.displayCutout)
+ || first.logicalDensityDpi != second.logicalDensityDpi
+ || !Objects.equals(first.roundedCorners, second.roundedCorners)
+ || !Objects.equals(first.displayShape, second.displayShape)
+ || !Objects.equals(first.uniqueId, second.uniqueId)
+ || !Objects.equals(first.address, second.address)
+ ) {
+ diff |= DIFF_WM_DEFERRABLE;
+ }
+
+ return diff;
+ }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index a840973..3f20624 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -276,6 +276,7 @@
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
+import static com.android.window.flags.Flags.deferDisplayUpdates;
/**
* Utility class for keeping track of the WindowStates and other pertinent contents of a
@@ -545,10 +546,12 @@
boolean isDefaultDisplay;
/** Detect user tapping outside of current focused task bounds .*/
+ // TODO(b/315321016): Remove once pointer event detection is removed from WM.
@VisibleForTesting
final TaskTapPointerEventListener mTapDetector;
/** Detect user tapping outside of current focused root task bounds .*/
+ // TODO(b/315321016): Remove once pointer event detection is removed from WM.
private Region mTouchExcludeRegion = new Region();
/** Save allocating when calculating rects */
@@ -1158,7 +1161,11 @@
mWallpaperController.resetLargestDisplay(display);
display.getDisplayInfo(mDisplayInfo);
display.getMetrics(mDisplayMetrics);
- mDisplayUpdater = new ImmediateDisplayUpdater(this);
+ if (deferDisplayUpdates()) {
+ mDisplayUpdater = new DeferredDisplayUpdater(this);
+ } else {
+ mDisplayUpdater = new ImmediateDisplayUpdater(this);
+ }
mSystemGestureExclusionLimit = mWmService.mConstants.mSystemGestureExclusionLimitDp
* mDisplayMetrics.densityDpi / DENSITY_DEFAULT;
isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
@@ -1189,12 +1196,18 @@
"PointerEventDispatcher" + mDisplayId, mDisplayId);
mPointerEventDispatcher = new PointerEventDispatcher(inputChannel);
- // Tap Listeners are supported for:
- // 1. All physical displays (multi-display).
- // 2. VirtualDisplays on VR, AA (and everything else).
- mTapDetector = new TaskTapPointerEventListener(mWmService, this);
- registerPointerEventListener(mTapDetector);
- registerPointerEventListener(mWmService.mMousePositionTracker);
+ if (com.android.input.flags.Flags.removePointerEventTrackingInWm()) {
+ mTapDetector = null;
+ } else {
+ // Tap Listeners are supported for:
+ // 1. All physical displays (multi-display).
+ // 2. VirtualDisplays on VR, AA (and everything else).
+ mTapDetector = new TaskTapPointerEventListener(mWmService, this);
+ registerPointerEventListener(mTapDetector);
+ }
+ if (mWmService.mMousePositionTracker != null) {
+ registerPointerEventListener(mWmService.mMousePositionTracker);
+ }
if (mWmService.mAtmService.getRecentTasks() != null) {
registerPointerEventListener(
mWmService.mAtmService.getRecentTasks().getInputListener());
@@ -1636,12 +1649,13 @@
mWmService.mWindowPlacerLocked.performSurfacePlacement();
}
- void sendNewConfiguration() {
+ /** Returns {@code true} if the display configuration is changed. */
+ boolean sendNewConfiguration() {
if (!isReady()) {
- return;
+ return false;
}
if (mRemoteDisplayChangeController.isWaitingForRemoteDisplayChange()) {
- return;
+ return false;
}
final Transition.ReadyCondition displayConfig = mTransitionController.isCollecting()
@@ -1656,7 +1670,7 @@
displayConfig.meet();
}
if (configUpdated) {
- return;
+ return true;
}
// The display configuration doesn't change. If there is a launching transformed app, that
@@ -1674,6 +1688,7 @@
setLayoutNeeded();
mWmService.mWindowPlacerLocked.performSurfacePlacement();
}
+ return false;
}
@Override
@@ -3260,6 +3275,12 @@
}
void updateTouchExcludeRegion() {
+ if (mTapDetector == null) {
+ // The touch exclude region is used to detect the region outside of the focused task
+ // so that the tap detector can detect outside touches. Don't calculate the exclude
+ // region when the tap detector is disabled.
+ return;
+ }
final Task focusedTask = (mFocusedApp != null ? mFocusedApp.getTask() : null);
if (focusedTask == null) {
mTouchExcludeRegion.setEmpty();
@@ -3298,6 +3319,11 @@
}
private void processTaskForTouchExcludeRegion(Task task, Task focusedTask, int delta) {
+ if (mTapDetector == null) {
+ // The touch exclude region is used to detect the region outside of the focused task
+ // so that the tap detector can detect outside touches. Don't calculate the exclude
+ // region when the tap detector is disabled.
+ }
final ActivityRecord topVisibleActivity = task.getTopVisibleActivity();
if (topVisibleActivity == null || !topVisibleActivity.hasContentToDisplay()) {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index b862d7c..460a68f 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -39,6 +39,7 @@
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_CONSUME_IME_INSETS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
@@ -272,6 +273,8 @@
private @InsetsType int mForciblyShownTypes;
+ private boolean mImeInsetsConsumed;
+
private boolean mIsImmersiveMode;
// The windows we were told about in focusChanged.
@@ -1420,6 +1423,7 @@
mShowingDream = false;
mIsFreeformWindowOverlappingWithNavBar = false;
mForciblyShownTypes = 0;
+ mImeInsetsConsumed = false;
}
/**
@@ -1481,6 +1485,17 @@
mForciblyShownTypes |= win.mAttrs.forciblyShownTypes;
}
+ if (win.mImeInsetsConsumed != mImeInsetsConsumed) {
+ win.mImeInsetsConsumed = mImeInsetsConsumed;
+ final WindowState imeWin = mDisplayContent.mInputMethodWindow;
+ if (win.isReadyToDispatchInsetsState() && imeWin != null && imeWin.isVisible()) {
+ win.notifyInsetsChanged();
+ }
+ }
+ if ((attrs.privateFlags & PRIVATE_FLAG_CONSUME_IME_INSETS) != 0 && win.isVisible()) {
+ mImeInsetsConsumed = true;
+ }
+
if (!affectsSystemUi) {
return;
}
@@ -2828,6 +2843,7 @@
}
}
pw.print(prefix); pw.print("mTopIsFullscreen="); pw.println(mTopIsFullscreen);
+ pw.print(prefix); pw.print("mImeInsetsConsumed="); pw.println(mImeInsetsConsumed);
pw.print(prefix); pw.print("mForceShowNavigationBarEnabled=");
pw.print(mForceShowNavigationBarEnabled);
pw.print(" mAllowLockscreenWhenOn="); pw.println(mAllowLockscreenWhenOn);
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index c089d10..7815679 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -391,13 +391,26 @@
if (originalImeSource != null) {
final boolean imeVisibility = w.isRequestedVisible(Type.ime());
- final InsetsState state = copyState ? new InsetsState(originalState)
+ final InsetsState state = copyState
+ ? new InsetsState(originalState)
: originalState;
final InsetsSource imeSource = new InsetsSource(originalImeSource);
imeSource.setVisible(imeVisibility);
state.addSource(imeSource);
return state;
}
+ } else if (w.mImeInsetsConsumed) {
+ // Set the IME source (if there is one) to be invisible if it has been consumed.
+ final InsetsSource originalImeSource = originalState.peekSource(ID_IME);
+ if (originalImeSource != null && originalImeSource.isVisible()) {
+ final InsetsState state = copyState
+ ? new InsetsState(originalState)
+ : originalState;
+ final InsetsSource imeSource = new InsetsSource(originalImeSource);
+ imeSource.setVisible(false);
+ state.addSource(imeSource);
+ return state;
+ }
}
return originalState;
}
diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index 23c135a..03574029 100644
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -26,6 +26,7 @@
import android.view.Display.Mode;
import android.view.DisplayInfo;
import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.SurfaceControl.RefreshRateRange;
import java.util.HashMap;
@@ -191,26 +192,35 @@
public static class FrameRateVote {
float mRefreshRate;
@Surface.FrameRateCompatibility int mCompatibility;
+ @SurfaceControl.FrameRateSelectionStrategy int mSelectionStrategy;
- FrameRateVote(float refreshRate, @Surface.FrameRateCompatibility int compatibility) {
- update(refreshRate, compatibility);
+
+
+ FrameRateVote(float refreshRate, @Surface.FrameRateCompatibility int compatibility,
+ @SurfaceControl.FrameRateSelectionStrategy int selectionStrategy) {
+ update(refreshRate, compatibility, selectionStrategy);
}
FrameRateVote() {
reset();
}
- boolean update(float refreshRate, @Surface.FrameRateCompatibility int compatibility) {
- if (!refreshRateEquals(refreshRate) || mCompatibility != compatibility) {
+ boolean update(float refreshRate, @Surface.FrameRateCompatibility int compatibility,
+ @SurfaceControl.FrameRateSelectionStrategy int selectionStrategy) {
+ if (!refreshRateEquals(refreshRate)
+ || mCompatibility != compatibility
+ || mSelectionStrategy != selectionStrategy) {
mRefreshRate = refreshRate;
mCompatibility = compatibility;
+ mSelectionStrategy = selectionStrategy;
return true;
}
return false;
}
boolean reset() {
- return update(0, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+ return update(0, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+ SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_PROPAGATE);
}
@Override
@@ -221,17 +231,20 @@
FrameRateVote other = (FrameRateVote) o;
return refreshRateEquals(other.mRefreshRate)
- && mCompatibility == other.mCompatibility;
+ && mCompatibility == other.mCompatibility
+ && mSelectionStrategy == other.mSelectionStrategy;
}
@Override
public int hashCode() {
- return Objects.hash(mRefreshRate, mCompatibility);
+ return Objects.hash(mRefreshRate, mCompatibility, mSelectionStrategy);
+
}
@Override
public String toString() {
- return "mRefreshRate=" + mRefreshRate + ", mCompatibility=" + mCompatibility;
+ return "mRefreshRate=" + mRefreshRate + ", mCompatibility=" + mCompatibility
+ + ", mSelectionStrategy=" + mSelectionStrategy;
}
private boolean refreshRateEquals(float refreshRate) {
@@ -265,7 +278,8 @@
for (Display.Mode mode : mDisplayInfo.supportedModes) {
if (preferredModeId == mode.getModeId()) {
return w.mFrameRateVote.update(mode.getRefreshRate(),
- Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+ Surface.FRAME_RATE_COMPATIBILITY_EXACT,
+ SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
}
}
}
@@ -273,7 +287,8 @@
if (w.mAttrs.preferredRefreshRate > 0) {
return w.mFrameRateVote.update(w.mAttrs.preferredRefreshRate,
- Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+ Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+ SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
}
// If the app didn't set a preferred mode id or refresh rate, but it is part of the deny
@@ -282,7 +297,8 @@
final String packageName = w.getOwningPackage();
if (mHighRefreshRateDenylist.isDenylisted(packageName)) {
return w.mFrameRateVote.update(mLowRefreshRateMode.getRefreshRate(),
- Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+ Surface.FRAME_RATE_COMPATIBILITY_EXACT,
+ SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
}
}
diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
index 7d22b74..ac244c7 100644
--- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
@@ -45,6 +45,10 @@
public TaskTapPointerEventListener(WindowManagerService service,
DisplayContent displayContent) {
+ // TODO(b/315321016): Remove this class when the flag rollout is complete.
+ if (com.android.input.flags.Flags.removePointerEventTrackingInWm()) {
+ throw new IllegalStateException("TaskTapPointerEventListener should not be used!");
+ }
mService = service;
mDisplayContent = displayContent;
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 76c4a0e..f020bfa 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2868,8 +2868,7 @@
final WindowContainer<?> wc = mParticipants.valueAt(i);
final DisplayContent dc = wc.asDisplayContent();
if (dc == null || !mChanges.get(dc).hasChanged()) continue;
- final int originalSeq = dc.getConfiguration().seq;
- dc.sendNewConfiguration();
+ final boolean changed = dc.sendNewConfiguration();
// Set to ready if no other change controls the ready state. But if there is, such as
// if an activity is pausing, it will call setReady(ar, false) and wait for the next
// resumed activity. Then do not set to ready because the transition only contains
@@ -2877,7 +2876,7 @@
if (!mReadyTrackerOld.mUsed) {
setReady(dc, true);
}
- if (originalSeq == dc.getConfiguration().seq) continue;
+ if (!changed) continue;
// If the update is deferred, sendNewConfiguration won't deliver new configuration to
// clients, then it is the caller's responsibility to deliver the changes.
if (mController.mAtm.mTaskSupervisor.isRootVisibilityUpdateDeferred()) {
diff --git a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
new file mode 100644
index 0000000..1688a1a
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
@@ -0,0 +1,448 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.graphics.Matrix.MSCALE_X;
+import static android.graphics.Matrix.MSCALE_Y;
+import static android.graphics.Matrix.MSKEW_X;
+import static android.graphics.Matrix.MSKEW_Y;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TPL;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.IntArray;
+import android.util.Pair;
+import android.util.Size;
+import android.view.InputWindowHandle;
+import android.window.ITrustedPresentationListener;
+import android.window.TrustedPresentationThresholds;
+import android.window.WindowInfosListener;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.server.wm.utils.RegionUtils;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Optional;
+
+/**
+ * Class to handle TrustedPresentationListener registrations in a thread safe manner. This class
+ * also takes care of cleaning up listeners when the remote process dies.
+ */
+public class TrustedPresentationListenerController {
+
+ // Should only be accessed by the posting to the handler
+ private class Listeners {
+ private final class ListenerDeathRecipient implements IBinder.DeathRecipient {
+ IBinder mListenerBinder;
+ int mInstances;
+
+ ListenerDeathRecipient(IBinder listenerBinder) {
+ mListenerBinder = listenerBinder;
+ mInstances = 0;
+ try {
+ mListenerBinder.linkToDeath(this, 0);
+ } catch (RemoteException ignore) {
+ }
+ }
+
+ void addInstance() {
+ mInstances++;
+ }
+
+ // return true if there are no instances alive
+ boolean removeInstance() {
+ mInstances--;
+ if (mInstances > 0) {
+ return false;
+ }
+ mListenerBinder.unlinkToDeath(this, 0);
+ return true;
+ }
+
+ public void binderDied() {
+ mHandler.post(() -> {
+ mUniqueListeners.remove(mListenerBinder);
+ removeListeners(mListenerBinder, Optional.empty());
+ });
+ }
+ }
+
+ // tracks binder deaths for cleanup
+ ArrayMap<IBinder, ListenerDeathRecipient> mUniqueListeners = new ArrayMap<>();
+ ArrayMap<IBinder /*window*/, ArrayList<TrustedPresentationInfo>> mWindowToListeners =
+ new ArrayMap<>();
+
+ void register(IBinder window, ITrustedPresentationListener listener,
+ TrustedPresentationThresholds thresholds, int id) {
+ var listenersForWindow = mWindowToListeners.computeIfAbsent(window,
+ iBinder -> new ArrayList<>());
+ listenersForWindow.add(new TrustedPresentationInfo(thresholds, id, listener));
+
+ // register death listener
+ var listenerBinder = listener.asBinder();
+ var deathRecipient = mUniqueListeners.computeIfAbsent(listenerBinder,
+ ListenerDeathRecipient::new);
+ deathRecipient.addInstance();
+ }
+
+ void unregister(ITrustedPresentationListener trustedPresentationListener, int id) {
+ var listenerBinder = trustedPresentationListener.asBinder();
+ var deathRecipient = mUniqueListeners.get(listenerBinder);
+ if (deathRecipient == null) {
+ ProtoLog.e(WM_DEBUG_TPL, "unregister failed, couldn't find"
+ + " deathRecipient for %s with id=%d", trustedPresentationListener, id);
+ return;
+ }
+
+ if (deathRecipient.removeInstance()) {
+ mUniqueListeners.remove(listenerBinder);
+ }
+ removeListeners(listenerBinder, Optional.of(id));
+ }
+
+ boolean isEmpty() {
+ return mWindowToListeners.isEmpty();
+ }
+
+ ArrayList<TrustedPresentationInfo> get(IBinder windowToken) {
+ return mWindowToListeners.get(windowToken);
+ }
+
+ private void removeListeners(IBinder listenerBinder, Optional<Integer> id) {
+ for (int i = mWindowToListeners.size() - 1; i >= 0; i--) {
+ var listeners = mWindowToListeners.valueAt(i);
+ for (int j = listeners.size() - 1; j >= 0; j--) {
+ var listener = listeners.get(j);
+ if (listener.mListener.asBinder() == listenerBinder && (id.isEmpty()
+ || listener.mId == id.get())) {
+ listeners.remove(j);
+ }
+ }
+ if (listeners.isEmpty()) {
+ mWindowToListeners.removeAt(i);
+ }
+ }
+ }
+ }
+
+ private final Object mHandlerThreadLock = new Object();
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+
+ private WindowInfosListener mWindowInfosListener;
+
+ Listeners mRegisteredListeners = new Listeners();
+
+ private InputWindowHandle[] mLastWindowHandles;
+
+ private final Object mIgnoredWindowTokensLock = new Object();
+
+ private final ArraySet<IBinder> mIgnoredWindowTokens = new ArraySet<>();
+
+ private void startHandlerThreadIfNeeded() {
+ synchronized (mHandlerThreadLock) {
+ if (mHandler == null) {
+ mHandlerThread = new HandlerThread("WindowInfosListenerForTpl");
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ }
+ }
+ }
+
+ void addIgnoredWindowTokens(IBinder token) {
+ synchronized (mIgnoredWindowTokensLock) {
+ mIgnoredWindowTokens.add(token);
+ }
+ }
+
+ void removeIgnoredWindowTokens(IBinder token) {
+ synchronized (mIgnoredWindowTokensLock) {
+ mIgnoredWindowTokens.remove(token);
+ }
+ }
+
+ void registerListener(IBinder window, ITrustedPresentationListener listener,
+ TrustedPresentationThresholds thresholds, int id) {
+ startHandlerThreadIfNeeded();
+ mHandler.post(() -> {
+ ProtoLog.d(WM_DEBUG_TPL, "Registering listener=%s with id=%d for window=%s with %s",
+ listener, id, window, thresholds);
+
+ mRegisteredListeners.register(window, listener, thresholds, id);
+ registerWindowInfosListener();
+ // Update the initial state for the new registered listener
+ computeTpl(mLastWindowHandles);
+ });
+ }
+
+ void unregisterListener(ITrustedPresentationListener listener, int id) {
+ startHandlerThreadIfNeeded();
+ mHandler.post(() -> {
+ ProtoLog.d(WM_DEBUG_TPL, "Unregistering listener=%s with id=%d",
+ listener, id);
+
+ mRegisteredListeners.unregister(listener, id);
+ if (mRegisteredListeners.isEmpty()) {
+ unregisterWindowInfosListener();
+ }
+ });
+ }
+
+ void dump(PrintWriter pw) {
+ final String innerPrefix = " ";
+ pw.println("TrustedPresentationListenerController:");
+ pw.println(innerPrefix + "Active unique listeners ("
+ + mRegisteredListeners.mUniqueListeners.size() + "):");
+ for (int i = 0; i < mRegisteredListeners.mWindowToListeners.size(); i++) {
+ pw.println(
+ innerPrefix + " window=" + mRegisteredListeners.mWindowToListeners.keyAt(i));
+ final var listeners = mRegisteredListeners.mWindowToListeners.valueAt(i);
+ for (int j = 0; j < listeners.size(); j++) {
+ final var listener = listeners.get(j);
+ pw.println(innerPrefix + innerPrefix + " listener=" + listener.mListener.asBinder()
+ + " id=" + listener.mId
+ + " thresholds=" + listener.mThresholds);
+ }
+ }
+ }
+
+ private void registerWindowInfosListener() {
+ if (mWindowInfosListener != null) {
+ return;
+ }
+
+ mWindowInfosListener = new WindowInfosListener() {
+ @Override
+ public void onWindowInfosChanged(InputWindowHandle[] windowHandles,
+ DisplayInfo[] displayInfos) {
+ mHandler.post(() -> computeTpl(windowHandles));
+ }
+ };
+ mLastWindowHandles = mWindowInfosListener.register().first;
+ }
+
+ private void unregisterWindowInfosListener() {
+ if (mWindowInfosListener == null) {
+ return;
+ }
+
+ mWindowInfosListener.unregister();
+ mWindowInfosListener = null;
+ mLastWindowHandles = null;
+ }
+
+ private void computeTpl(InputWindowHandle[] windowHandles) {
+ mLastWindowHandles = windowHandles;
+ if (mLastWindowHandles == null || mLastWindowHandles.length == 0
+ || mRegisteredListeners.isEmpty()) {
+ return;
+ }
+
+ Rect tmpRect = new Rect();
+ Matrix tmpInverseMatrix = new Matrix();
+ float[] tmpMatrix = new float[9];
+ Region coveredRegionsAbove = new Region();
+ long currTimeMs = System.currentTimeMillis();
+ ProtoLog.v(WM_DEBUG_TPL, "Checking %d windows", mLastWindowHandles.length);
+
+ ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates =
+ new ArrayMap<>();
+ ArraySet<IBinder> ignoredWindowTokens;
+ synchronized (mIgnoredWindowTokensLock) {
+ ignoredWindowTokens = new ArraySet<>(mIgnoredWindowTokens);
+ }
+ for (var windowHandle : mLastWindowHandles) {
+ if (ignoredWindowTokens.contains(windowHandle.getWindowToken())) {
+ ProtoLog.v(WM_DEBUG_TPL, "Skipping %s", windowHandle.name);
+ continue;
+ }
+ tmpRect.set(windowHandle.frame);
+ var listeners = mRegisteredListeners.get(windowHandle.getWindowToken());
+ if (listeners != null) {
+ Region region = new Region();
+ region.op(tmpRect, coveredRegionsAbove, Region.Op.DIFFERENCE);
+ windowHandle.transform.invert(tmpInverseMatrix);
+ tmpInverseMatrix.getValues(tmpMatrix);
+ float scaleX = (float) Math.sqrt(tmpMatrix[MSCALE_X] * tmpMatrix[MSCALE_X]
+ + tmpMatrix[MSKEW_X] * tmpMatrix[MSKEW_X]);
+ float scaleY = (float) Math.sqrt(tmpMatrix[MSCALE_Y] * tmpMatrix[MSCALE_Y]
+ + tmpMatrix[MSKEW_Y] * tmpMatrix[MSKEW_Y]);
+
+ float fractionRendered = computeFractionRendered(region, new RectF(tmpRect),
+ windowHandle.contentSize,
+ scaleX, scaleY);
+
+ checkIfInThreshold(listeners, listenerUpdates, fractionRendered, windowHandle.alpha,
+ currTimeMs);
+ }
+
+ coveredRegionsAbove.op(tmpRect, Region.Op.UNION);
+ ProtoLog.v(WM_DEBUG_TPL, "coveredRegionsAbove updated with %s frame:%s region:%s",
+ windowHandle.name, tmpRect.toShortString(), coveredRegionsAbove);
+ }
+
+ for (int i = 0; i < listenerUpdates.size(); i++) {
+ var updates = listenerUpdates.valueAt(i);
+ var listener = listenerUpdates.keyAt(i);
+ try {
+ listener.onTrustedPresentationChanged(updates.first.toArray(),
+ updates.second.toArray());
+ } catch (RemoteException ignore) {
+ }
+ }
+ }
+
+ private void addListenerUpdate(
+ ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates,
+ ITrustedPresentationListener listener, int id, boolean presentationState) {
+ var updates = listenerUpdates.get(listener);
+ if (updates == null) {
+ updates = new Pair<>(new IntArray(), new IntArray());
+ listenerUpdates.put(listener, updates);
+ }
+ if (presentationState) {
+ updates.first.add(id);
+ } else {
+ updates.second.add(id);
+ }
+ }
+
+
+ private void checkIfInThreshold(
+ ArrayList<TrustedPresentationInfo> listeners,
+ ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates,
+ float fractionRendered, float alpha, long currTimeMs) {
+ ProtoLog.v(WM_DEBUG_TPL, "checkIfInThreshold fractionRendered=%f alpha=%f currTimeMs=%d",
+ fractionRendered, alpha, currTimeMs);
+ for (int i = 0; i < listeners.size(); i++) {
+ var trustedPresentationInfo = listeners.get(i);
+ var listener = trustedPresentationInfo.mListener;
+ boolean lastState = trustedPresentationInfo.mLastComputedTrustedPresentationState;
+ boolean newState =
+ (alpha >= trustedPresentationInfo.mThresholds.minAlpha) && (fractionRendered
+ >= trustedPresentationInfo.mThresholds.minFractionRendered);
+ trustedPresentationInfo.mLastComputedTrustedPresentationState = newState;
+
+ ProtoLog.v(WM_DEBUG_TPL,
+ "lastState=%s newState=%s alpha=%f minAlpha=%f fractionRendered=%f "
+ + "minFractionRendered=%f",
+ lastState, newState, alpha, trustedPresentationInfo.mThresholds.minAlpha,
+ fractionRendered, trustedPresentationInfo.mThresholds.minFractionRendered);
+
+ if (lastState && !newState) {
+ // We were in the trusted presentation state, but now we left it,
+ // emit the callback if needed
+ if (trustedPresentationInfo.mLastReportedTrustedPresentationState) {
+ trustedPresentationInfo.mLastReportedTrustedPresentationState = false;
+ addListenerUpdate(listenerUpdates, listener,
+ trustedPresentationInfo.mId, /*presentationState*/ false);
+ ProtoLog.d(WM_DEBUG_TPL, "Adding untrusted state listener=%s with id=%d",
+ listener, trustedPresentationInfo.mId);
+ }
+ // Reset the timer
+ trustedPresentationInfo.mEnteredTrustedPresentationStateTime = -1;
+ } else if (!lastState && newState) {
+ // We were not in the trusted presentation state, but we entered it, begin the timer
+ // and make sure this gets called at least once more!
+ trustedPresentationInfo.mEnteredTrustedPresentationStateTime = currTimeMs;
+ mHandler.postDelayed(() -> {
+ computeTpl(mLastWindowHandles);
+ }, (long) (trustedPresentationInfo.mThresholds.stabilityRequirementMs * 1.5));
+ }
+
+ // Has the timer elapsed, but we are still in the state? Emit a callback if needed
+ if (!trustedPresentationInfo.mLastReportedTrustedPresentationState && newState && (
+ currTimeMs - trustedPresentationInfo.mEnteredTrustedPresentationStateTime
+ > trustedPresentationInfo.mThresholds.stabilityRequirementMs)) {
+ trustedPresentationInfo.mLastReportedTrustedPresentationState = true;
+ addListenerUpdate(listenerUpdates, listener,
+ trustedPresentationInfo.mId, /*presentationState*/ true);
+ ProtoLog.d(WM_DEBUG_TPL, "Adding trusted state listener=%s with id=%d",
+ listener, trustedPresentationInfo.mId);
+ }
+ }
+ }
+
+ private float computeFractionRendered(Region visibleRegion, RectF screenBounds,
+ Size contentSize,
+ float sx, float sy) {
+ ProtoLog.v(WM_DEBUG_TPL,
+ "computeFractionRendered: visibleRegion=%s screenBounds=%s contentSize=%s "
+ + "scale=%f,%f",
+ visibleRegion, screenBounds, contentSize, sx, sy);
+
+ if (contentSize.getWidth() == 0 || contentSize.getHeight() == 0) {
+ return -1;
+ }
+ if (screenBounds.width() == 0 || screenBounds.height() == 0) {
+ return -1;
+ }
+
+ float fractionRendered = Math.min(sx * sy, 1.0f);
+ ProtoLog.v(WM_DEBUG_TPL, "fractionRendered scale=%f", fractionRendered);
+
+ float boundsOverSourceW = screenBounds.width() / (float) contentSize.getWidth();
+ float boundsOverSourceH = screenBounds.height() / (float) contentSize.getHeight();
+ fractionRendered *= boundsOverSourceW * boundsOverSourceH;
+ ProtoLog.v(WM_DEBUG_TPL, "fractionRendered boundsOverSource=%f", fractionRendered);
+ // Compute the size of all the rects since they may be disconnected.
+ float[] visibleSize = new float[1];
+ RegionUtils.forEachRect(visibleRegion, rect -> {
+ float size = rect.width() * rect.height();
+ visibleSize[0] += size;
+ });
+
+ fractionRendered *= visibleSize[0] / (screenBounds.width() * screenBounds.height());
+ return fractionRendered;
+ }
+
+ private static class TrustedPresentationInfo {
+ boolean mLastComputedTrustedPresentationState = false;
+ boolean mLastReportedTrustedPresentationState = false;
+ long mEnteredTrustedPresentationStateTime = -1;
+ final TrustedPresentationThresholds mThresholds;
+
+ final ITrustedPresentationListener mListener;
+ final int mId;
+
+ private TrustedPresentationInfo(TrustedPresentationThresholds thresholds, int id,
+ ITrustedPresentationListener listener) {
+ mThresholds = thresholds;
+ mId = id;
+ mListener = listener;
+ checkValid(thresholds);
+ }
+
+ private void checkValid(TrustedPresentationThresholds thresholds) {
+ if (thresholds.minAlpha <= 0 || thresholds.minFractionRendered <= 0
+ || thresholds.stabilityRequirementMs < 1) {
+ throw new IllegalArgumentException(
+ "TrustedPresentationThresholds values are invalid");
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 72632dc..10dd334 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -303,9 +303,11 @@
import android.window.ClientWindowFrames;
import android.window.ISurfaceSyncGroupCompletedListener;
import android.window.ITaskFpsCallback;
+import android.window.ITrustedPresentationListener;
import android.window.ScreenCapture;
import android.window.SystemPerformanceHinter;
import android.window.TaskSnapshot;
+import android.window.TrustedPresentationThresholds;
import android.window.WindowContainerToken;
import android.window.WindowContextInfo;
@@ -764,6 +766,9 @@
private final SurfaceSyncGroupController mSurfaceSyncGroupController =
new SurfaceSyncGroupController();
+ final TrustedPresentationListenerController mTrustedPresentationListenerController =
+ new TrustedPresentationListenerController();
+
@VisibleForTesting
final class SettingsObserver extends ContentObserver {
private final Uri mDisplayInversionEnabledUri =
@@ -7171,6 +7176,7 @@
pw.println(separator);
}
mSystemPerformanceHinter.dump(pw, "");
+ mTrustedPresentationListenerController.dump(pw);
}
}
@@ -7302,7 +7308,12 @@
}
}
- MousePositionTracker mMousePositionTracker = new MousePositionTracker();
+ // The mouse position tracker will be obsolete after the Pointer Icon Refactor.
+ // TODO(b/293587049): Remove after the refactoring is fully rolled out.
+ @Nullable
+ final MousePositionTracker mMousePositionTracker =
+ com.android.input.flags.Flags.enablePointerChoreographer() ? null
+ : new MousePositionTracker();
private static class MousePositionTracker implements PointerEventListener {
private boolean mLatestEventWasMouse;
@@ -7354,6 +7365,9 @@
};
void updatePointerIcon(IWindow client) {
+ if (mMousePositionTracker == null) {
+ return;
+ }
int pointerDisplayId;
float mouseX, mouseY;
@@ -7400,6 +7414,9 @@
}
void restorePointerIconLocked(DisplayContent displayContent, float latestX, float latestY) {
+ if (mMousePositionTracker == null) {
+ return;
+ }
// Mouse position tracker has not been getting updates while dragging, update it now.
if (!mMousePositionTracker.updatePosition(
displayContent.getDisplayId(), latestX, latestY)) {
@@ -7423,6 +7440,9 @@
}
}
void setMousePointerDisplayId(int displayId) {
+ if (mMousePositionTracker == null) {
+ return;
+ }
mMousePositionTracker.setPointerDisplayId(displayId);
}
@@ -9771,4 +9791,17 @@
Binder.restoreCallingIdentity(origId);
}
}
+
+ @Override
+ public void registerTrustedPresentationListener(IBinder window,
+ ITrustedPresentationListener listener,
+ TrustedPresentationThresholds thresholds, int id) {
+ mTrustedPresentationListenerController.registerListener(window, listener, thresholds, id);
+ }
+
+ @Override
+ public void unregisterTrustedPresentationListener(ITrustedPresentationListener listener,
+ int id) {
+ mTrustedPresentationListenerController.unregisterListener(listener, id);
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b890a9e..4e9d23c 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -178,6 +178,7 @@
import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY;
import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER;
import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES;
+import static com.android.window.flags.Flags.explicitRefreshRateHints;
import static com.android.window.flags.Flags.secureWindowState;
import static com.android.window.flags.Flags.surfaceTrustedOverlay;
@@ -668,6 +669,12 @@
boolean mSeamlesslyRotated = false;
/**
+ * Whether the IME insets have been consumed. If {@code true}, this window won't be able to
+ * receive visible IME insets; {@code false}, otherwise.
+ */
+ boolean mImeInsetsConsumed = false;
+
+ /**
* The insets state of sources provided by windows above the current window.
*/
final InsetsState mAboveInsetsState = new InsetsState();
@@ -1189,6 +1196,11 @@
ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Adding %s to %s", this, parentWindow);
parentWindow.addChild(this, sWindowSubLayerComparator);
}
+
+ if (token.mRoundedCornerOverlay) {
+ mWmService.mTrustedPresentationListenerController.addIgnoredWindowTokens(
+ getWindowToken());
+ }
}
@Override
@@ -1487,7 +1499,9 @@
if (insetsChanged) {
mWindowFrames.setInsetsChanged(false);
- mWmService.mWindowsInsetsChanged--;
+ if (mWmService.mWindowsInsetsChanged > 0) {
+ mWmService.mWindowsInsetsChanged--;
+ }
if (mWmService.mWindowsInsetsChanged == 0) {
mWmService.mH.removeMessages(WindowManagerService.H.INSETS_CHANGED);
}
@@ -2393,6 +2407,9 @@
}
mWmService.postWindowRemoveCleanupLocked(this);
+
+ mWmService.mTrustedPresentationListenerController.removeIgnoredWindowTokens(
+ getWindowToken());
}
@Override
@@ -3796,13 +3813,15 @@
*/
void notifyInsetsChanged() {
ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "notifyInsetsChanged for %s ", this);
- mWindowFrames.setInsetsChanged(true);
+ if (!mWindowFrames.hasInsetsChanged()) {
+ mWindowFrames.setInsetsChanged(true);
- // If the new InsetsState won't be dispatched before releasing WM lock, the following
- // message will be executed.
- mWmService.mWindowsInsetsChanged++;
- mWmService.mH.removeMessages(WindowManagerService.H.INSETS_CHANGED);
- mWmService.mH.sendEmptyMessage(WindowManagerService.H.INSETS_CHANGED);
+ // If the new InsetsState won't be dispatched before releasing WM lock, the following
+ // message will be executed.
+ mWmService.mWindowsInsetsChanged++;
+ mWmService.mH.removeMessages(WindowManagerService.H.INSETS_CHANGED);
+ mWmService.mH.sendEmptyMessage(WindowManagerService.H.INSETS_CHANGED);
+ }
final WindowContainer p = getParent();
if (p != null) {
@@ -4192,6 +4211,7 @@
} else {
pw.print("null");
}
+ pw.println();
if (mXOffset != 0 || mYOffset != 0) {
pw.println(prefix + "mXOffset=" + mXOffset + " mYOffset=" + mYOffset);
@@ -4225,6 +4245,9 @@
if (computeDragResizing()) {
pw.println(prefix + "computeDragResizing=" + computeDragResizing());
}
+ if (mImeInsetsConsumed) {
+ pw.println(prefix + "mImeInsetsConsumed=true");
+ }
pw.println(prefix + "isOnScreen=" + isOnScreen());
pw.println(prefix + "isVisible=" + isVisible());
pw.println(prefix + "keepClearAreas: restricted=" + mKeepClearAreas
@@ -5185,9 +5208,13 @@
boolean voteChanged = refreshRatePolicy.updateFrameRateVote(this);
if (voteChanged) {
- getPendingTransaction().setFrameRate(
- mSurfaceControl, mFrameRateVote.mRefreshRate,
- mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS);
+ getPendingTransaction()
+ .setFrameRate(mSurfaceControl, mFrameRateVote.mRefreshRate,
+ mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS);
+ if (explicitRefreshRateHints()) {
+ getPendingTransaction().setFrameRateSelectionStrategy(mSurfaceControl,
+ mFrameRateVote.mSelectionStrategy);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/utils/DisplayInfoOverrides.java b/services/core/java/com/android/server/wm/utils/DisplayInfoOverrides.java
index 8c8f6a6..193a0c8 100644
--- a/services/core/java/com/android/server/wm/utils/DisplayInfoOverrides.java
+++ b/services/core/java/com/android/server/wm/utils/DisplayInfoOverrides.java
@@ -30,7 +30,7 @@
* Set of DisplayInfo fields that are overridden in DisplayManager using values from
* WindowManager
*/
- public static final DisplayInfoFields WM_OVERRIDE_FIELDS = (out, source) -> {
+ public static final DisplayInfoFieldsUpdater WM_OVERRIDE_FIELDS = (out, source) -> {
out.appWidth = source.appWidth;
out.appHeight = source.appHeight;
out.smallestNominalAppWidth = source.smallestNominalAppWidth;
@@ -55,7 +55,7 @@
public static void copyDisplayInfoFields(@NonNull DisplayInfo out,
@NonNull DisplayInfo base,
@Nullable DisplayInfo override,
- @NonNull DisplayInfoFields fields) {
+ @NonNull DisplayInfoFieldsUpdater fields) {
out.copyFrom(base);
if (override != null) {
@@ -66,7 +66,7 @@
/**
* Callback interface that allows to specify a subset of fields of DisplayInfo object
*/
- public interface DisplayInfoFields {
+ public interface DisplayInfoFieldsUpdater {
/**
* Copies a subset of fields from {@param source} to {@param out}
*
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 24ee163..b19f3d8 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -187,7 +187,7 @@
"android.hardware.power.stats@1.0",
"android.hardware.power.stats-V1-ndk",
"android.hardware.thermal@1.0",
- "android.hardware.thermal-V1-ndk",
+ "android.hardware.thermal-V2-ndk",
"android.hardware.tv.input@1.0",
"android.hardware.tv.input-V2-ndk",
"android.hardware.vibrator-V2-cpp",
diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
index 44609ac..ea5fb5d6 100644
--- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
@@ -304,7 +304,7 @@
/** These permissions are supported for virtual devices. */
// TODO: b/298661870 - Use new API to get the list of device aware permissions.
val DEVICE_AWARE_PERMISSIONS =
- if (Flags.deviceAwarePermissionApis()) {
+ if (Flags.deviceAwarePermissionApisEnabled()) {
setOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
} else {
emptySet<String>()
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index a7d3249..0f65494 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -1551,7 +1551,8 @@
permissionName: String,
deviceId: Int,
): Int {
- return if (!Flags.deviceAwarePermissionApis() || deviceId == Context.DEVICE_ID_DEFAULT) {
+ return if (!Flags.deviceAwarePermissionApisEnabled() ||
+ deviceId == Context.DEVICE_ID_DEFAULT) {
with(policy) { getPermissionFlags(appId, userId, permissionName) }
} else {
if (permissionName !in DevicePermissionPolicy.DEVICE_AWARE_PERMISSIONS) {
@@ -1586,7 +1587,8 @@
deviceId: Int,
flags: Int
): Boolean {
- return if (!Flags.deviceAwarePermissionApis() || deviceId == Context.DEVICE_ID_DEFAULT) {
+ return if (!Flags.deviceAwarePermissionApisEnabled() ||
+ deviceId == Context.DEVICE_ID_DEFAULT) {
with(policy) { setPermissionFlags(appId, userId, permissionName, flags) }
} else {
if (permissionName !in DevicePermissionPolicy.DEVICE_AWARE_PERMISSIONS) {
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index a0dc2b6..f07e820 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -817,12 +817,14 @@
ps1.setUsesSdkLibraries(new String[] { "com.example.sdk.one" });
ps1.setUsesSdkLibrariesVersionsMajor(new long[] { 12 });
+ ps1.setUsesSdkLibrariesOptional(new boolean[] {true});
ps1.setFlags(ps1.getFlags() | ApplicationInfo.FLAG_SYSTEM);
settingsUnderTest.mPackages.put(PACKAGE_NAME_1, ps1);
assertThat(settingsUnderTest.disableSystemPackageLPw(PACKAGE_NAME_1, false), is(true));
ps2.setUsesSdkLibraries(new String[] { "com.example.sdk.two" });
ps2.setUsesSdkLibrariesVersionsMajor(new long[] { 34 });
+ ps2.setUsesSdkLibrariesOptional(new boolean[] {false});
settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2);
settingsUnderTest.writeLPr(computer, /*sync=*/true);
@@ -838,19 +840,30 @@
Truth.assertThat(readPs1).isNotNull();
Truth.assertThat(readPs1.getUsesSdkLibraries()).isNotNull();
Truth.assertThat(readPs1.getUsesSdkLibrariesVersionsMajor()).isNotNull();
+ Truth.assertThat(readPs1.getUsesSdkLibrariesOptional()).isNotNull();
Truth.assertThat(readPs2).isNotNull();
Truth.assertThat(readPs2.getUsesSdkLibraries()).isNotNull();
Truth.assertThat(readPs2.getUsesSdkLibrariesVersionsMajor()).isNotNull();
+ Truth.assertThat(readPs2.getUsesSdkLibrariesOptional()).isNotNull();
List<Long> ps1VersionsAsList = new ArrayList<>();
for (long version : ps1.getUsesSdkLibrariesVersionsMajor()) {
ps1VersionsAsList.add(version);
}
+ List<Boolean> ps1RequireAsList = new ArrayList<>();
+ for (boolean optional : ps1.getUsesSdkLibrariesOptional()) {
+ ps1RequireAsList.add(optional);
+ }
+
List<Long> ps2VersionsAsList = new ArrayList<>();
for (long version : ps2.getUsesSdkLibrariesVersionsMajor()) {
ps2VersionsAsList.add(version);
}
+ List<Boolean> ps2RequireAsList = new ArrayList<>();
+ for (boolean optional : ps2.getUsesSdkLibrariesOptional()) {
+ ps2RequireAsList.add(optional);
+ }
Truth.assertThat(readPs1.getUsesSdkLibraries()).asList()
.containsExactlyElementsIn(ps1.getUsesSdkLibraries()).inOrder();
@@ -858,11 +871,17 @@
Truth.assertThat(readPs1.getUsesSdkLibrariesVersionsMajor()).asList()
.containsExactlyElementsIn(ps1VersionsAsList).inOrder();
+ Truth.assertThat(readPs1.getUsesSdkLibrariesOptional()).asList()
+ .containsExactlyElementsIn(ps1RequireAsList).inOrder();
+
Truth.assertThat(readPs2.getUsesSdkLibraries()).asList()
.containsExactlyElementsIn(ps2.getUsesSdkLibraries()).inOrder();
Truth.assertThat(readPs2.getUsesSdkLibrariesVersionsMajor()).asList()
.containsExactlyElementsIn(ps2VersionsAsList).inOrder();
+
+ Truth.assertThat(readPs2.getUsesSdkLibrariesOptional()).asList()
+ .containsExactlyElementsIn(ps2RequireAsList).inOrder();
}
@Test
@@ -1047,6 +1066,7 @@
UserManagerService.getInstance(),
null /*usesSdkLibraries*/,
null /*usesSdkLibrariesVersions*/,
+ null /*usesSdkLibrariesOptional*/,
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
@@ -1087,6 +1107,7 @@
UserManagerService.getInstance(),
null /*usesSdkLibraries*/,
null /*usesSdkLibrariesVersions*/,
+ null /*usesSdkLibrariesOptional*/,
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
@@ -1129,6 +1150,7 @@
UserManagerService.getInstance(),
null /*usesSdkLibraries*/,
null /*usesSdkLibrariesVersions*/,
+ null /*usesSdkLibrariesOptional*/,
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
@@ -1167,6 +1189,7 @@
UserManagerService.getInstance(),
null /*usesSdkLibraries*/,
null /*usesSdkLibrariesVersions*/,
+ null /*usesSdkLibrariesOptional*/,
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
@@ -1214,6 +1237,7 @@
UserManagerService.getInstance(),
null /*usesSdkLibraries*/,
null /*usesSdkLibrariesVersions*/,
+ null /*usesSdkLibrariesOptional*/,
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
@@ -1263,6 +1287,7 @@
null /*usesSdkLibrariesVersions*/,
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
+ null /*usesSdkLibrariesOptional*/,
null /*mimeGroups*/,
UUID.randomUUID(),
34 /*targetSdkVersion*/,
@@ -1311,6 +1336,7 @@
null /*usesSdkLibrariesVersions*/,
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
+ null /*usesSdkLibrariesOptional*/,
null /*mimeGroups*/,
UUID.randomUUID(),
34 /*targetSdkVersion*/,
@@ -1356,6 +1382,7 @@
null /*usesSdkLibrariesVersions*/,
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
+ null /*usesSdkLibrariesOptional*/,
null /*mimeGroups*/,
UUID.randomUUID(),
34 /*targetSdkVersion*/,
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
index ea88ec2..a62cd4f 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
@@ -1062,7 +1062,7 @@
.addProtectedBroadcast("foo8")
.setSdkLibraryName("sdk12")
.setSdkLibVersionMajor(42)
- .addUsesSdkLibrary("sdk23", 200, new String[]{"digest2"})
+ .addUsesSdkLibrary("sdk23", 200, new String[]{"digest2"}, true)
.setStaticSharedLibraryName("foo23")
.setStaticSharedLibraryVersion(100)
.addUsesStaticLibrary("foo23", 100, new String[]{"digest"})
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
index decb44c..6202908 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
@@ -599,8 +599,8 @@
.setVolumeUuid(UUID_ONE.toString())
.addUsesStaticLibrary("some.static.library", 234L, new String[]{"testCert1"})
.addUsesStaticLibrary("some.other.static.library", 456L, new String[]{"testCert2"})
- .addUsesSdkLibrary("some.sdk.library", 123L, new String[]{"testCert3"})
- .addUsesSdkLibrary("some.other.sdk.library", 789L, new String[]{"testCert4"})
+ .addUsesSdkLibrary("some.sdk.library", 123L, new String[]{"testCert3"}, false)
+ .addUsesSdkLibrary("some.other.sdk.library", 789L, new String[]{"testCert4"}, true)
.hideAsParsed())
.setNativeLibraryRootDir("/data/tmp/randompath/base.apk:/lib")
.setVersionCodeMajor(1)
@@ -628,6 +628,7 @@
assertThat(pkgSetting.getUsesSdkLibraries(),
arrayContaining("some.sdk.library", "some.other.sdk.library"));
assertThat(pkgSetting.getUsesSdkLibrariesVersionsMajor(), is(new long[]{123L, 789L}));
+ assertThat(pkgSetting.getUsesSdkLibrariesOptional(), is(new boolean[]{false, true}));
assertThat(pkgSetting.getPkg(), is(scanResult.mRequest.mParsedPackage));
assertThat(pkgSetting.getPath(), is(new File(createCodePath(packageName))));
assertThat(pkgSetting.getVersionCode(),
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index 170faf6..09b66c1 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -127,6 +127,7 @@
"getUsesSdkLibraries",
"getUsesSdkLibrariesVersionsMajor",
"getUsesSdkLibrariesCertDigests",
+ "getUsesSdkLibrariesOptional",
// Tested through addUsesStaticLibrary
"addUsesStaticLibrary",
"getUsesStaticLibraries",
@@ -608,7 +609,7 @@
.setSplitHasCode(1, false)
.setSplitClassLoaderName(0, "testSplitClassLoaderNameZero")
.setSplitClassLoaderName(1, "testSplitClassLoaderNameOne")
- .addUsesSdkLibrary("testSdk", 2L, arrayOf("testCertDigest1"))
+ .addUsesSdkLibrary("testSdk", 2L, arrayOf("testCertDigest1"), true)
.addUsesStaticLibrary("testStatic", 3L, arrayOf("testCertDigest2"))
override fun finalizeObject(parcelable: Parcelable) {
@@ -661,6 +662,7 @@
expect.that(after.usesSdkLibrariesCertDigests!!.size).isEqualTo(1)
expect.that(after.usesSdkLibrariesCertDigests!![0]).asList()
.containsExactly("testCertDigest1")
+ expect.that(after.usesSdkLibrariesOptional).asList().containsExactly(true)
expect.that(after.usesStaticLibraries).containsExactly("testStatic")
expect.that(after.usesStaticLibrariesVersions).asList().containsExactly(3L)
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index a250ac7..0702764 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -368,10 +368,15 @@
}
}
+ private JobInfo.Builder createJobInfoBuilder(int jobId) {
+ return new JobInfo.Builder(jobId, new ComponentName(mContext, "TestQuotaJobService"));
+ }
+
private JobStatus createJobStatus(String testTag, int jobId) {
- JobInfo jobInfo = new JobInfo.Builder(jobId,
- new ComponentName(mContext, "TestQuotaJobService"))
- .build();
+ return createJobStatus(testTag, createJobInfoBuilder(jobId).build());
+ }
+
+ private JobStatus createJobStatus(String testTag, JobInfo jobInfo) {
return createJobStatus(testTag, SOURCE_PACKAGE, CALLING_UID, jobInfo);
}
@@ -1333,39 +1338,70 @@
mQuotaController.saveTimingSession(0, SOURCE_PACKAGE,
createTimingSession(sElapsedRealtimeClock.millis() - (6 * MINUTE_IN_MILLIS),
3 * MINUTE_IN_MILLIS, 5), false);
+ final long timeUntilQuotaConsumedMs = 7 * MINUTE_IN_MILLIS;
JobStatus job = createJobStatus("testGetMaxJobExecutionTimeLocked", 0);
+ //noinspection deprecation
+ JobStatus jobDefIWF = createJobStatus("testGetMaxJobExecutionTimeLocked",
+ createJobInfoBuilder(1)
+ .setImportantWhileForeground(true)
+ .setPriority(JobInfo.PRIORITY_DEFAULT)
+ .build());
+ JobStatus jobHigh = createJobStatus("testGetMaxJobExecutionTimeLocked",
+ createJobInfoBuilder(2).setPriority(JobInfo.PRIORITY_HIGH).build());
setStandbyBucket(RARE_INDEX, job);
+ setStandbyBucket(RARE_INDEX, jobDefIWF);
+ setStandbyBucket(RARE_INDEX, jobHigh);
setCharging();
synchronized (mQuotaController.mLock) {
assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
+ assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF)));
+ assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mQuotaController.getMaxJobExecutionTimeMsLocked((jobHigh)));
}
setDischarging();
setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
synchronized (mQuotaController.mLock) {
- assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ assertEquals(timeUntilQuotaConsumedMs,
mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
+ assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF)));
+ assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mQuotaController.getMaxJobExecutionTimeMsLocked((jobHigh)));
}
// Top-started job
setProcessState(ActivityManager.PROCESS_STATE_TOP);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeStartTrackingJobLocked(job, null);
+ trackJobs(job, jobDefIWF, jobHigh);
mQuotaController.prepareForExecutionLocked(job);
+ mQuotaController.prepareForExecutionLocked(jobDefIWF);
+ mQuotaController.prepareForExecutionLocked(jobHigh);
}
setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
synchronized (mQuotaController.mLock) {
- assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ assertEquals(timeUntilQuotaConsumedMs,
mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
+ assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF)));
+ assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mQuotaController.getMaxJobExecutionTimeMsLocked((jobHigh)));
mQuotaController.maybeStopTrackingJobLocked(job, null);
+ mQuotaController.maybeStopTrackingJobLocked(jobDefIWF, null);
+ mQuotaController.maybeStopTrackingJobLocked(jobHigh, null);
}
setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
synchronized (mQuotaController.mLock) {
- assertEquals(7 * MINUTE_IN_MILLIS,
+ assertEquals(timeUntilQuotaConsumedMs,
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ assertEquals(timeUntilQuotaConsumedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(jobDefIWF));
+ assertEquals(timeUntilQuotaConsumedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(jobHigh));
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
index cf81f0a..e131a98 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
@@ -16,12 +16,14 @@
package com.android.server.pm
+import android.app.AppOpsManager
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Binder
import com.android.server.testutils.any
import com.android.server.testutils.eq
import com.android.server.testutils.nullable
+import com.android.server.testutils.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -31,6 +33,7 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+
@RunWith(JUnit4::class)
class DistractingPackageHelperTest : PackageHelperTestBase() {
@@ -40,6 +43,9 @@
super.setup()
distractingPackageHelper = DistractingPackageHelper(
pms, broadcastHelper, suspendPackageHelper)
+ whenever(rule.mocks().appOpsManager.checkOpNoThrow(
+ eq(AppOpsManager.OP_SYSTEM_EXEMPT_FROM_SUSPENSION), any(), any()))
+ .thenReturn(AppOpsManager.MODE_DEFAULT)
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 46806f3..28bd987 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -15,6 +15,7 @@
*/
package com.android.server.pm
+import android.app.AppOpsManager
import android.app.PropertyInvalidatedCache
import android.content.Context
import android.content.Intent
@@ -151,7 +152,7 @@
.mockStatic(EventLog::class.java)
.mockStatic(LocalServices::class.java)
.mockStatic(LocalManagerRegistry::class.java)
- .mockStatic(DeviceConfig::class.java)
+ .mockStatic(DeviceConfig::class.java, Mockito.CALLS_REAL_METHODS)
.mockStatic(HexEncoding::class.java)
.apply(withSession)
session = apply.startMocking()
@@ -192,6 +193,7 @@
val userManagerService: UserManagerService = mock()
val componentResolver: ComponentResolver = mock()
val permissionManagerInternal: PermissionManagerServiceInternal = mock()
+ val appOpsManager: AppOpsManager = mock()
val incrementalManager: IncrementalManager = mock()
val platformCompat: PlatformCompat = mock()
val settings: Settings = mock()
@@ -304,6 +306,7 @@
whenever(mocks.injector.defaultAppProvider) { mocks.defaultAppProvider }
whenever(mocks.injector.backgroundHandler) { mocks.backgroundHandler }
whenever(mocks.injector.updateOwnershipHelper) { mocks.updateOwnershipHelper }
+ whenever(mocks.injector.getSystemService(AppOpsManager::class.java)) { mocks.appOpsManager }
wheneverStatic { SystemConfig.getInstance() }.thenReturn(mocks.systemConfig)
whenever(mocks.systemConfig.availableFeatures).thenReturn(DEFAULT_AVAILABLE_FEATURES_MAP)
whenever(mocks.systemConfig.sharedLibraries).thenReturn(DEFAULT_SHARED_LIBRARIES_LIST)
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
index 7b381ce..1473033 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -16,12 +16,14 @@
package com.android.server.pm
+import android.app.AppOpsManager
import android.content.Intent
import android.content.pm.SuspendDialogInfo
import android.os.Binder
import android.os.PersistableBundle
import com.android.server.testutils.any
import com.android.server.testutils.eq
+import com.android.server.testutils.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -32,6 +34,12 @@
@RunWith(JUnit4::class)
class SuspendPackageHelperTest : PackageHelperTestBase() {
+ override fun setup() {
+ super.setup()
+ whenever(rule.mocks().appOpsManager.checkOpNoThrow(
+ eq(AppOpsManager.OP_SYSTEM_EXEMPT_FROM_SUSPENSION), any(), any()))
+ .thenReturn(AppOpsManager.MODE_DEFAULT)
+ }
@Test
fun setPackagesSuspended() {
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index f02e5a5..07197b1 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -12,6 +12,7 @@
],
exclude_srcs: [
+ "src/com/android/server/power/stats/MultiStateStatsTest.java",
"src/com/android/server/power/stats/PowerStatsStoreTest.java",
],
@@ -62,13 +63,12 @@
static_libs: [
"services.core",
"modules-utils-binary-xml",
-
"androidx.annotation_annotation",
"androidx.test.rules",
],
srcs: [
+ "src/com/android/server/power/stats/MultiStateStatsTest.java",
"src/com/android/server/power/stats/PowerStatsStoreTest.java",
],
- sdk_version: "test_current",
auto_gen_config: true,
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java
index 48e2dd7..6d61dc8 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java
@@ -26,8 +26,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.os.MultiStateStats;
-
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java
index 8ca4ff6..993d834 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java
@@ -38,7 +38,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.os.MultiStateStats;
import com.android.internal.os.PowerProfile;
import com.android.internal.os.PowerStats;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
index eb03a6c..e8f46b3 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
@@ -22,12 +22,12 @@
import static org.junit.Assert.assertThrows;
import android.os.BatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.os.MultiStateStats;
-
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -39,6 +39,10 @@
@SmallTest
public class MultiStateStatsTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .build();
+
public static final int DIMENSION_COUNT = 2;
@Test
diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
index 9b047f2..6537d47 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
@@ -40,6 +40,7 @@
mediaSharedWithParent='true'
credentialShareableWithParent='false'
authAlwaysRequiredToDisableQuietMode='true'
+ allowStoppingUserWithDelayedLocking='true'
showInSettings='23'
hideInSettingsInQuietMode='true'
inheritDevicePolicy='450'
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index a9967f6..71d64cf 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -532,12 +532,11 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
- public void testTwoFingerTripleTap_StateIsIdle_shouldInActivated() {
+ public void testTwoFingerDoubleTap_StateIsIdle_shouldInActivated() {
goFromStateIdleTo(STATE_IDLE);
twoFingerTap();
twoFingerTap();
- twoFingerTap();
assertIn(STATE_ACTIVATED);
verify(mMockMagnificationLogger, never()).logMagnificationTripleTap(anyBoolean());
@@ -546,13 +545,12 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
- public void testTwoFingerTripleTap_StateIsActivated_shouldInIdle() {
+ public void testTwoFingerDoubleTap_StateIsActivated_shouldInIdle() {
goFromStateIdleTo(STATE_ACTIVATED);
reset(mMockMagnificationLogger);
twoFingerTap();
twoFingerTap();
- twoFingerTap();
assertIn(STATE_IDLE);
verify(mMockMagnificationLogger, never()).logMagnificationTripleTap(anyBoolean());
@@ -561,11 +559,10 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
- public void testTwoFingerTripleTapAndHold_StateIsIdle_shouldZoomsImmediately() {
+ public void testTwoFingerDoubleTapAndHold_StateIsIdle_shouldZoomsImmediately() {
goFromStateIdleTo(STATE_IDLE);
twoFingerTap();
- twoFingerTap();
twoFingerTapAndHold();
assertIn(STATE_NON_ACTIVATED_ZOOMED_TMP);
@@ -575,11 +572,10 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
- public void testTwoFingerTripleSwipeAndHold_StateIsIdle_shouldZoomsImmediately() {
+ public void testTwoFingerDoubleSwipeAndHold_StateIsIdle_shouldZoomsImmediately() {
goFromStateIdleTo(STATE_IDLE);
twoFingerTap();
- twoFingerTap();
twoFingerSwipeAndHold();
assertIn(STATE_NON_ACTIVATED_ZOOMED_TMP);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
index a3b67ae..c99e040 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
@@ -72,15 +72,15 @@
public static final int STATE_SHOW_MAGNIFIER_TRIPLE_TAP = 4;
public static final int STATE_NOT_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD = 5;
public static final int STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD = 6;
- public static final int STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP = 7;
- public static final int STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD = 8;
- public static final int STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD = 9;
+ public static final int STATE_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP = 7;
+ public static final int STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD = 8;
+ public static final int STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD = 9;
//TODO: Test it after can injecting Handler to GestureMatcher is available.
public static final int FIRST_STATE = STATE_IDLE;
public static final int LAST_STATE = STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD;
public static final int LAST_STATE_WITH_MULTI_FINGER =
- STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD;
+ STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD;
// Co-prime x and y, to potentially catch x-y-swapped errors
public static final float DEFAULT_TAP_X = 301;
@@ -257,15 +257,15 @@
break;
case STATE_SHOW_MAGNIFIER_SHORTCUT:
case STATE_SHOW_MAGNIFIER_TRIPLE_TAP:
- case STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP:
+ case STATE_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP:
check(isWindowMagnifierEnabled(DISPLAY_0), state);
check(mWindowMagnificationGestureHandler.mCurrentState
== mWindowMagnificationGestureHandler.mDetectingState, state);
break;
case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD:
case STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD:
- case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD:
- case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: {
+ case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD:
+ case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD: {
check(isWindowMagnifierEnabled(DISPLAY_0), state);
check(mWindowMagnificationGestureHandler.mCurrentState
== mWindowMagnificationGestureHandler.mViewportDraggingState, state);
@@ -337,8 +337,7 @@
tapAndHold();
}
break;
- case STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP: {
- twoFingerTap();
+ case STATE_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP: {
twoFingerTap();
twoFingerTap();
// Wait for two-finger tap gesture completed.
@@ -346,17 +345,15 @@
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
break;
- case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: {
- twoFingerTap();
+ case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD: {
twoFingerTap();
twoFingerTapAndHold();
}
break;
- case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: {
+ case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD: {
// enabled then perform two finger triple tap and hold gesture
goFromStateIdleTo(STATE_SHOW_MAGNIFIER_SHORTCUT);
twoFingerTap();
- twoFingerTap();
twoFingerTapAndHold();
}
break;
@@ -394,16 +391,15 @@
}
break;
case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD:
- case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD:
+ case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD:
send(upEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y));
break;
case STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD:
- case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD:
+ case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD:
send(upEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y));
returnToNormalFrom(STATE_SHOW_MAGNIFIER_SHORTCUT);
break;
- case STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP: {
- twoFingerTap();
+ case STATE_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP: {
twoFingerTap();
twoFingerTap();
// Wait for two-finger tap gesture completed.
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index d26d671..77b1455 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -92,6 +92,7 @@
import android.os.UserManager;
import android.os.storage.IStorageManager;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.Log;
import android.view.Display;
@@ -104,11 +105,14 @@
import com.android.server.pm.UserJourneyLogger;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerService;
+import com.android.server.pm.UserTypeDetails;
+import com.android.server.pm.UserTypeFactory;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerService;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
@@ -175,6 +179,9 @@
USER_START_MSG,
REPORT_LOCKED_BOOT_COMPLETE_MSG);
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setUp() throws Exception {
runWithDexmakerShareClassLoader(() -> {
@@ -789,30 +796,101 @@
}
@Test
- public void testStartProfile() throws Exception {
- setUpAndStartProfileInBackground(TEST_USER_ID1);
+ public void testStartManagedProfile() throws Exception {
+ setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED);
startBackgroundUserAssertions();
verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY);
}
@Test
- public void testStartProfile_whenUsersOnSecondaryDisplaysIsEnabled() throws Exception {
+ public void testStartManagedProfile_whenUsersOnSecondaryDisplaysIsEnabled() throws Exception {
mockIsUsersOnSecondaryDisplaysEnabled(true);
- setUpAndStartProfileInBackground(TEST_USER_ID1);
+ setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED);
startBackgroundUserAssertions();
verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY);
}
@Test
- public void testStopProfile() throws Exception {
- setUpAndStartProfileInBackground(TEST_USER_ID1);
+ public void testStopManagedProfile() throws Exception {
+ setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED);
assertProfileLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* expectLocking= */ true);
verifyUserUnassignedFromDisplay(TEST_USER_ID1);
}
+ @Test
+ public void testStopPrivateProfile() throws Exception {
+ mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+ setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
+ assertProfileLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* expectLocking= */ true);
+ verifyUserUnassignedFromDisplay(TEST_USER_ID1);
+
+ mSetFlagsRule.disableFlags(
+ android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+ setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_PRIVATE);
+ assertProfileLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* expectLocking= */ true);
+ verifyUserUnassignedFromDisplay(TEST_USER_ID2);
+ }
+
+ @Test
+ public void testStopPrivateProfileWithDelayedLocking() throws Exception {
+ mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+ setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+ /* keyEvictedCallback */ null, /* expectLocking= */ false);
+ }
+
+ @Test
+ public void testStopPrivateProfileWithDelayedLocking_flagDisabled() throws Exception {
+ mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.disableFlags(
+ android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+ setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+ /* keyEvictedCallback */ null, /* expectLocking= */ true);
+
+ mSetFlagsRule.disableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(
+ android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+ setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_PRIVATE);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ true,
+ /* keyEvictedCallback */ null, /* expectLocking= */ true);
+ }
+
+ @Test
+ public void testStopPrivateProfileWithDelayedLocking_maxRunningUsersBreached()
+ throws Exception {
+ mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
+ /* maxRunningUsers= */ 1, /* delayUserDataLocking= */ false);
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+ setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
+ setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_MANAGED);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+ /* keyEvictedCallback */ null, /* expectLocking= */ true);
+ }
+
+ @Test
+ public void testStopManagedProfileWithDelayedLocking() throws Exception {
+ mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+ setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+ /* keyEvictedCallback */ null, /* expectLocking= */ true);
+ }
+
/** Tests handleIncomingUser() for a variety of permissions and situations. */
@Test
public void testHandleIncomingUser() throws Exception {
@@ -1001,8 +1079,8 @@
mUserStates.put(userId, mUserController.getStartedUserState(userId));
}
- private void setUpAndStartProfileInBackground(int userId) throws Exception {
- setUpUser(userId, UserInfo.FLAG_PROFILE, false, UserManager.USER_TYPE_PROFILE_MANAGED);
+ private void setUpAndStartProfileInBackground(int userId, String userType) throws Exception {
+ setUpUser(userId, UserInfo.FLAG_PROFILE, false, userType);
assertThat(mUserController.startProfile(userId, /* evenWhenDisabled=*/ false,
/* unlockListener= */ null)).isTrue();
@@ -1070,6 +1148,11 @@
userInfo.preCreated = preCreated;
when(mInjector.mUserManagerMock.getUserInfo(eq(userId))).thenReturn(userInfo);
when(mInjector.mUserManagerMock.isPreCreated(userId)).thenReturn(preCreated);
+
+ UserTypeDetails userTypeDetails = UserTypeFactory.getUserTypes().get(userType);
+ assertThat(userTypeDetails).isNotNull();
+ when(mInjector.mUserManagerInternalMock.getUserProperties(eq(userId)))
+ .thenReturn(userTypeDetails.getDefaultUserPropertiesReference());
}
private static List<String> getActions(List<Intent> intents) {
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
index cc3c880..f1c1dc3 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
@@ -266,18 +266,22 @@
.adoptShellPermissionIdentity(Manifest.permission.BLUETOOTH_PRIVILEGED);
// no metadata set
- assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
- DEVICE_TYPE_DEFAULT.getBytes()));
- assertFalse(
- mAudioDeviceBroker.isBluetoothAudioDeviceCategoryFixed(
- mFakeBtDevice.getAddress()));
+ if (mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
+ DEVICE_TYPE_DEFAULT.getBytes())) {
+ assertFalse(mAudioDeviceBroker.isBluetoothAudioDeviceCategoryFixed(
+ mFakeBtDevice.getAddress()));
+ }
// metadata set
- assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
- DEVICE_TYPE_HEADSET.getBytes()));
- assertTrue(mAudioDeviceBroker.isBluetoothAudioDeviceCategoryFixed(
- mFakeBtDevice.getAddress()));
+ if (mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
+ DEVICE_TYPE_HEADSET.getBytes())) {
+ assertTrue(mAudioDeviceBroker.isBluetoothAudioDeviceCategoryFixed(
+ mFakeBtDevice.getAddress()));
+ }
} finally {
+ // reset the metadata device type
+ mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
+ DEVICE_TYPE_DEFAULT.getBytes());
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.dropShellPermissionIdentity();
}
@@ -304,25 +308,30 @@
.adoptShellPermissionIdentity(Manifest.permission.BLUETOOTH_PRIVILEGED);
// no metadata set
- assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
- DEVICE_TYPE_DEFAULT.getBytes()));
- assertEquals(AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER,
- mAudioDeviceBroker.getAndUpdateBtAdiDeviceStateCategoryForAddress(
- mFakeBtDevice.getAddress()));
- verify(mMockAudioService,
- timeout(MAX_MESSAGE_HANDLING_DELAY_MS).times(0)).onUpdatedAdiDeviceState(
- eq(devState));
+ if (mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
+ DEVICE_TYPE_DEFAULT.getBytes())) {
+ assertEquals(AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER,
+ mAudioDeviceBroker.getAndUpdateBtAdiDeviceStateCategoryForAddress(
+ mFakeBtDevice.getAddress()));
+ verify(mMockAudioService,
+ timeout(MAX_MESSAGE_HANDLING_DELAY_MS).times(0)).onUpdatedAdiDeviceState(
+ eq(devState));
+ }
// metadata set
- assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
- DEVICE_TYPE_HEADSET.getBytes()));
- assertEquals(AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES,
- mAudioDeviceBroker.getAndUpdateBtAdiDeviceStateCategoryForAddress(
- mFakeBtDevice.getAddress()));
- verify(mMockAudioService,
- timeout(MAX_MESSAGE_HANDLING_DELAY_MS)).onUpdatedAdiDeviceState(
- any());
+ if (mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
+ DEVICE_TYPE_HEADSET.getBytes())) {
+ assertEquals(AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES,
+ mAudioDeviceBroker.getAndUpdateBtAdiDeviceStateCategoryForAddress(
+ mFakeBtDevice.getAddress()));
+ verify(mMockAudioService,
+ timeout(MAX_MESSAGE_HANDLING_DELAY_MS)).onUpdatedAdiDeviceState(
+ any());
+ }
} finally {
+ // reset the metadata device type
+ mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
+ DEVICE_TYPE_DEFAULT.getBytes());
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.dropShellPermissionIdentity();
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
index 3355910..18e6f0a 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
@@ -151,6 +151,19 @@
}
@Test
+ public void userNotAllowlisted_launchIsBlocked() {
+ GenericWindowPolicyController gwpc = createGwpcWithNoAllowedUsers();
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
+
+ ActivityInfo activityInfo = getActivityInfo(
+ NONBLOCKED_APP_PACKAGE_NAME,
+ NONBLOCKED_APP_PACKAGE_NAME,
+ /* displayOnRemoteDevices */ true,
+ /* targetDisplayCategory */ null);
+ assertActivityIsBlocked(gwpc, activityInfo);
+ }
+
+ @Test
public void openNonBlockedAppOnVirtualDisplay_isNotBlocked() {
GenericWindowPolicyController gwpc = createGwpc();
gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
@@ -702,6 +715,26 @@
/* customHomeComponent= */ null);
}
+ private GenericWindowPolicyController createGwpcWithNoAllowedUsers() {
+ return new GenericWindowPolicyController(
+ 0,
+ 0,
+ /* allowedUsers= */ new ArraySet<>(),
+ /* activityLaunchAllowedByDefault= */ true,
+ /* activityPolicyExemptions= */ new ArraySet<>(),
+ /* crossTaskNavigationAllowedByDefault= */ true,
+ /* crossTaskNavigationExemptions= */ new ArraySet<>(),
+ /* permissionDialogComponent= */ null,
+ /* activityListener= */ mActivityListener,
+ /* pipBlockedCallback= */ mPipBlockedCallback,
+ /* activityBlockedCallback= */ mActivityBlockedCallback,
+ /* secureWindowCallback= */ mSecureWindowCallback,
+ /* intentListenerCallback= */ mIntentListenerCallback,
+ /* displayCategories= */ new ArraySet<>(),
+ /* showTasksInHostDeviceRecents= */ true,
+ /* customHomeComponent= */ null);
+ }
+
private GenericWindowPolicyController createGwpcWithCustomHomeComponent(
ComponentName homeComponent) {
return new GenericWindowPolicyController(
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index 72cc969..d7ed7c2 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -69,6 +69,7 @@
.setMediaSharedWithParent(false)
.setCredentialShareableWithParent(true)
.setAuthAlwaysRequiredToDisableQuietMode(false)
+ .setAllowStoppingUserWithDelayedLocking(false)
.setDeleteAppWithParent(false)
.setAlwaysVisible(false)
.setCrossProfileContentSharingStrategy(0)
@@ -85,6 +86,7 @@
actualProps.setMediaSharedWithParent(true);
actualProps.setCredentialShareableWithParent(false);
actualProps.setAuthAlwaysRequiredToDisableQuietMode(true);
+ actualProps.setAllowStoppingUserWithDelayedLocking(true);
actualProps.setDeleteAppWithParent(true);
actualProps.setAlwaysVisible(true);
actualProps.setCrossProfileContentSharingStrategy(1);
@@ -130,6 +132,7 @@
.setMediaSharedWithParent(true)
.setDeleteAppWithParent(true)
.setAuthAlwaysRequiredToDisableQuietMode(false)
+ .setAllowStoppingUserWithDelayedLocking(false)
.setAlwaysVisible(true)
.build();
final UserProperties orig = new UserProperties(defaultProps);
@@ -139,6 +142,7 @@
orig.setInheritDevicePolicy(9456);
orig.setDeleteAppWithParent(false);
orig.setAuthAlwaysRequiredToDisableQuietMode(true);
+ orig.setAllowStoppingUserWithDelayedLocking(true);
orig.setAlwaysVisible(false);
// Test every permission level. (Currently, it's linear so it's easy.)
@@ -184,6 +188,8 @@
assertEqualGetterOrThrows(orig::getDeleteAppWithParent,
copy::getDeleteAppWithParent, exposeAll);
assertEqualGetterOrThrows(orig::getAlwaysVisible, copy::getAlwaysVisible, exposeAll);
+ assertEqualGetterOrThrows(orig::getAllowStoppingUserWithDelayedLocking,
+ copy::getAllowStoppingUserWithDelayedLocking, exposeAll);
// Items requiring hasManagePermission - put them here using hasManagePermission.
assertEqualGetterOrThrows(orig::getShowInSettings, copy::getShowInSettings,
@@ -258,6 +264,8 @@
.isEqualTo(actual.isCredentialShareableWithParent());
assertThat(expected.isAuthAlwaysRequiredToDisableQuietMode())
.isEqualTo(actual.isAuthAlwaysRequiredToDisableQuietMode());
+ assertThat(expected.getAllowStoppingUserWithDelayedLocking())
+ .isEqualTo(actual.getAllowStoppingUserWithDelayedLocking());
assertThat(expected.getDeleteAppWithParent()).isEqualTo(actual.getDeleteAppWithParent());
assertThat(expected.getAlwaysVisible()).isEqualTo(actual.getAlwaysVisible());
assertThat(expected.getCrossProfileContentSharingStrategy())
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index d0ad573..7083706 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -90,6 +90,7 @@
.setMediaSharedWithParent(true)
.setCredentialShareableWithParent(false)
.setAuthAlwaysRequiredToDisableQuietMode(true)
+ .setAllowStoppingUserWithDelayedLocking(true)
.setShowInSettings(900)
.setShowInSharingSurfaces(20)
.setShowInQuietMode(30)
@@ -167,6 +168,8 @@
assertFalse(type.getDefaultUserPropertiesReference().isCredentialShareableWithParent());
assertTrue(type.getDefaultUserPropertiesReference()
.isAuthAlwaysRequiredToDisableQuietMode());
+ assertTrue(type.getDefaultUserPropertiesReference()
+ .getAllowStoppingUserWithDelayedLocking());
assertEquals(900, type.getDefaultUserPropertiesReference().getShowInSettings());
assertEquals(20, type.getDefaultUserPropertiesReference().getShowInSharingSurfaces());
assertEquals(30,
@@ -322,6 +325,7 @@
.setMediaSharedWithParent(false)
.setCredentialShareableWithParent(true)
.setAuthAlwaysRequiredToDisableQuietMode(false)
+ .setAllowStoppingUserWithDelayedLocking(false)
.setShowInSettings(20)
.setInheritDevicePolicy(21)
.setShowInSharingSurfaces(22)
@@ -367,6 +371,8 @@
.isCredentialShareableWithParent());
assertFalse(aospType.getDefaultUserPropertiesReference()
.isAuthAlwaysRequiredToDisableQuietMode());
+ assertFalse(aospType.getDefaultUserPropertiesReference()
+ .getAllowStoppingUserWithDelayedLocking());
assertEquals(20, aospType.getDefaultUserPropertiesReference().getShowInSettings());
assertEquals(21, aospType.getDefaultUserPropertiesReference()
.getInheritDevicePolicy());
@@ -420,6 +426,8 @@
.isCredentialShareableWithParent());
assertTrue(aospType.getDefaultUserPropertiesReference()
.isAuthAlwaysRequiredToDisableQuietMode());
+ assertTrue(aospType.getDefaultUserPropertiesReference()
+ .getAllowStoppingUserWithDelayedLocking());
assertEquals(23, aospType.getDefaultUserPropertiesReference().getShowInSettings());
assertEquals(22,
aospType.getDefaultUserPropertiesReference().getShowInSharingSurfaces());
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index ced0bb5..a743fff 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -342,8 +342,12 @@
assertThat(typeProps.getCrossProfileContentSharingStrategy())
.isEqualTo(privateProfileUserProperties.getCrossProfileContentSharingStrategy());
assertThrows(SecurityException.class, privateProfileUserProperties::getDeleteAppWithParent);
+ assertThrows(SecurityException.class,
+ privateProfileUserProperties::getAllowStoppingUserWithDelayedLocking);
+
compareDrawables(mUserManager.getUserBadge(),
Resources.getSystem().getDrawable(userTypeDetails.getBadgePlain()));
+
// Verify private profile parent
assertThat(mUserManager.getProfileParent(mainUserId)).isNull();
UserInfo parentProfileInfo = mUserManager.getProfileParent(userInfo.id);
diff --git a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
index 330dbb8..861d14a 100644
--- a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
+++ b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
@@ -30,13 +30,19 @@
import com.android.internal.annotations.GuardedBy;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@SmallTest
@Presubmit
+@RunWith(Parameterized.class)
public class AnrTimerTest {
// The commonly used message timeout key.
@@ -106,6 +112,16 @@
}
/**
+ * Force AnrTimer to use the test parameter for the feature flag.
+ */
+ class TestInjector extends AnrTimer.Injector {
+ @Override
+ boolean anrTimerServiceEnabled() {
+ return mEnabled;
+ }
+ }
+
+ /**
* An instrumented AnrTimer.
*/
private static class TestAnrTimer extends AnrTimer<TestArg> {
@@ -137,6 +153,17 @@
assertEquals(actual.what, MSG_TIMEOUT);
}
+ @Parameters(name = "featureEnabled={0}")
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] { {false}, {true} });
+ }
+
+ /** True if the feature is enabled. */
+ private boolean mEnabled;
+
+ public AnrTimerTest(boolean featureEnabled) {
+ mEnabled = featureEnabled;
+ }
/**
* Verify that a simple expiration succeeds. The timer is started for 10ms. The test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
index 5febd02..8936dc6 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
@@ -16,13 +16,28 @@
package com.android.server.notification;
+import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
import android.app.UiModeManager;
import android.app.WallpaperManager;
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.hardware.display.ColorDisplayManager;
import android.os.PowerManager;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -36,6 +51,7 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -55,7 +71,7 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = new TestableContext(InstrumentationRegistry.getContext(), null);
+ mContext = spy(new TestableContext(InstrumentationRegistry.getContext(), null));
mContext.addMockSystemService(PowerManager.class, mPowerManager);
mContext.addMockSystemService(ColorDisplayManager.class, mColorDisplayManager);
mContext.addMockSystemService(UiModeManager.class, mUiModeManager);
@@ -74,25 +90,33 @@
.setShouldDisplayGrayscale(true)
.setShouldUseNightMode(true)
.build();
- mApplier.apply(effects);
+ mApplier.apply(effects, UPDATE_ORIGIN_USER);
verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
verify(mColorDisplayManager).setSaturationLevel(eq(0));
verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
- verifyZeroInteractions(mUiModeManager); // Coming later; adding now so test fails then. :)
+ verify(mUiModeManager).setNightModeActivatedForCustomMode(
+ eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
}
@Test
- public void apply_removesEffects() {
+ public void apply_removesPreviouslyAppliedEffects() {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ ZenDeviceEffects previousEffects = new ZenDeviceEffects.Builder()
+ .setShouldSuppressAmbientDisplay(true)
+ .setShouldDimWallpaper(true)
+ .build();
+ mApplier.apply(previousEffects, UPDATE_ORIGIN_USER);
+ verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
+ verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
+
ZenDeviceEffects noEffects = new ZenDeviceEffects.Builder().build();
- mApplier.apply(noEffects);
+ mApplier.apply(noEffects, UPDATE_ORIGIN_USER);
verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false));
- verify(mColorDisplayManager).setSaturationLevel(eq(100));
verify(mWallpaperManager).setWallpaperDimAmount(eq(0.0f));
- verifyZeroInteractions(mUiModeManager);
+ verifyZeroInteractions(mColorDisplayManager, mUiModeManager);
}
@Test
@@ -107,9 +131,104 @@
.setShouldDisplayGrayscale(true)
.setShouldUseNightMode(true)
.build();
- mApplier.apply(effects);
+ mApplier.apply(effects, UPDATE_ORIGIN_USER);
verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
// (And no crash from missing services).
}
+
+ @Test
+ public void apply_someEffects_onlyThoseEffectsApplied() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+ ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
+ .setShouldDimWallpaper(true)
+ .setShouldDisplayGrayscale(true)
+ .build();
+ mApplier.apply(effects, UPDATE_ORIGIN_USER);
+
+ verify(mColorDisplayManager).setSaturationLevel(eq(0));
+ verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
+
+ verify(mPowerManager, never()).suppressAmbientDisplay(anyString(), anyBoolean());
+ verify(mUiModeManager, never()).setNightModeActivatedForCustomMode(anyInt(), anyBoolean());
+ }
+
+ @Test
+ public void apply_onlyEffectDeltaApplied() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+ mApplier.apply(new ZenDeviceEffects.Builder().setShouldDimWallpaper(true).build(),
+ UPDATE_ORIGIN_USER);
+ verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
+
+ // Apply a second effect and remove the first one.
+ mApplier.apply(new ZenDeviceEffects.Builder().setShouldDisplayGrayscale(true).build(),
+ UPDATE_ORIGIN_USER);
+
+ // Wallpaper dimming was undone, Grayscale was applied, nothing else was touched.
+ verify(mWallpaperManager).setWallpaperDimAmount(eq(0.0f));
+ verify(mColorDisplayManager).setSaturationLevel(eq(0));
+ verifyZeroInteractions(mPowerManager);
+ verifyZeroInteractions(mUiModeManager);
+ }
+
+ @Test
+ public void apply_darkThemeFromApp_appliedOnScreenOff() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ ArgumentCaptor<IntentFilter> intentFilterCaptor =
+ ArgumentCaptor.forClass(IntentFilter.class);
+
+ when(mPowerManager.isInteractive()).thenReturn(true);
+
+ mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(),
+ UPDATE_ORIGIN_APP);
+
+ // Effect was not yet applied, but a broadcast receiver was registered.
+ verifyZeroInteractions(mUiModeManager);
+ verify(mContext).registerReceiver(broadcastReceiverCaptor.capture(),
+ intentFilterCaptor.capture(), anyInt());
+ assertThat(intentFilterCaptor.getValue().getAction(0)).isEqualTo(Intent.ACTION_SCREEN_OFF);
+ BroadcastReceiver screenOffReceiver = broadcastReceiverCaptor.getValue();
+
+ // Now the "screen off" event comes.
+ screenOffReceiver.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
+
+ // So the effect is applied, and we stopped listening for this event.
+ verify(mUiModeManager).setNightModeActivatedForCustomMode(
+ eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+ verify(mContext).unregisterReceiver(eq(screenOffReceiver));
+ }
+
+ @Test
+ public void apply_darkThemeFromAppWithScreenOff_appliedImmediately() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+ when(mPowerManager.isInteractive()).thenReturn(false);
+
+ mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(),
+ UPDATE_ORIGIN_APP);
+
+ // Effect was applied, and no broadcast receiver was registered.
+ verify(mUiModeManager).setNightModeActivatedForCustomMode(
+ eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+ verify(mContext, never()).registerReceiver(any(), any(), anyInt());
+ }
+
+ @Test
+ public void testDeviceEffects_darkThemeFromUser_appliedImmediately() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+ when(mPowerManager.isInteractive()).thenReturn(true);
+
+ mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(),
+ UPDATE_ORIGIN_USER);
+
+ // Effect was applied, and no broadcast receiver was registered.
+ verify(mUiModeManager).setNightModeActivatedForCustomMode(
+ eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+ verify(mContext, never()).registerReceiver(any(), any(), anyInt());
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index ee08fd2..8bb11a4 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -220,6 +220,7 @@
import android.service.notification.NotificationRankingUpdate;
import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
+import android.service.notification.ZenModeConfig;
import android.service.notification.ZenPolicy;
import android.telecom.TelecomManager;
import android.telephony.TelephonyManager;
@@ -8929,8 +8930,8 @@
mBinderService.addAutomaticZenRule(rule, "com.android.settings");
// verify that zen mode helper gets passed in a package name of "android"
- verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString(),
- anyInt(), eq(ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI)); // system call
+ verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule),
+ eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), anyInt());
}
@Test
@@ -8951,8 +8952,8 @@
mBinderService.addAutomaticZenRule(rule, "com.android.settings");
// verify that zen mode helper gets passed in a package name of "android"
- verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString(),
- anyInt(), eq(ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI)); // system call
+ verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule),
+ eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), anyInt());
}
@Test
@@ -8972,8 +8973,8 @@
// verify that zen mode helper gets passed in the package name from the arg, not the owner
verify(mockZenModeHelper).addAutomaticZenRule(
- eq("another.package"), eq(rule), anyString(), anyInt(),
- eq(ZenModeHelper.FROM_APP)); // doesn't count as a system/systemui call
+ eq("another.package"), eq(rule), eq(ZenModeConfig.UPDATE_ORIGIN_APP),
+ anyString(), anyInt()); // doesn't count as a system/systemui call
}
@Test
@@ -13185,7 +13186,7 @@
NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
mBinderService.setNotificationPolicy("package", policy);
- verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyBoolean());
+ verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
}
@Test
@@ -13207,7 +13208,7 @@
NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
mBinderService.setNotificationPolicy("package", policy);
- verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyBoolean());
+ verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
}
@Test
@@ -13223,7 +13224,7 @@
NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
mBinderService.setNotificationPolicy("package", policy);
- verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyBoolean());
+ verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
}
@Test
@@ -13271,7 +13272,8 @@
mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY);
verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
- eq("package"), anyString(), anyInt(), anyBoolean());
+ eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), eq("package"),
+ anyInt());
}
@Test
@@ -13280,6 +13282,7 @@
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
ZenModeHelper zenModeHelper = mock(ZenModeHelper.class);
mService.mZenModeHelper = zenModeHelper;
+ mService.setCallerIsNormalPackage();
when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
.thenReturn(true);
when(mCompanionMgr.getAssociations(anyString(), anyInt()))
@@ -13292,7 +13295,7 @@
mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY);
verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
- eq("package"), anyString(), anyInt(), anyBoolean());
+ eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyString(), eq("package"), anyInt());
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index fe21103..5ddac03 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -67,7 +67,6 @@
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -2370,7 +2369,7 @@
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false,
uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean());
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
resetZenModeHelper();
// create notification channel that can bypass dnd
@@ -2380,18 +2379,18 @@
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true,
uid, false);
assertTrue(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
resetZenModeHelper();
// delete channels
mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel.getId(), uid, false);
assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean());
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
resetZenModeHelper();
mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel2.getId(), uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
resetZenModeHelper();
}
@@ -2407,7 +2406,7 @@
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false,
uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean());
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
resetZenModeHelper();
// Recreate a channel & now the app has dnd access granted and can set the bypass dnd field
@@ -2417,7 +2416,7 @@
uid, false);
assertTrue(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
resetZenModeHelper();
}
@@ -2433,7 +2432,7 @@
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false,
uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean());
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
resetZenModeHelper();
// create notification channel that can bypass dnd, using local app level settings
@@ -2443,18 +2442,18 @@
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true,
uid, false);
assertTrue(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
resetZenModeHelper();
// delete channels
mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel.getId(), uid, false);
assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean());
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
resetZenModeHelper();
mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel2.getId(), uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
resetZenModeHelper();
}
@@ -2481,8 +2480,7 @@
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true,
uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(),
- anyInt(), anyBoolean());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
resetZenModeHelper();
}
@@ -2504,7 +2502,7 @@
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true,
uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
resetZenModeHelper();
}
@@ -2526,7 +2524,7 @@
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true,
uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
resetZenModeHelper();
}
@@ -2542,7 +2540,7 @@
mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false,
uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean());
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
resetZenModeHelper();
// update channel so it CAN bypass dnd:
@@ -2550,7 +2548,7 @@
channel.setBypassDnd(true);
mHelper.updateNotificationChannel(PKG_N_MR1, uid, channel, true, SYSTEM_UID, true);
assertTrue(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
resetZenModeHelper();
// update channel so it can't bypass dnd:
@@ -2558,7 +2556,7 @@
channel.setBypassDnd(false);
mHelper.updateNotificationChannel(PKG_N_MR1, uid, channel, true, SYSTEM_UID, true);
assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
resetZenModeHelper();
}
@@ -2571,7 +2569,7 @@
when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
mHelper.syncChannelsBypassingDnd();
assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
resetZenModeHelper();
}
@@ -2581,7 +2579,7 @@
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0, 0);
when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean());
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
resetZenModeHelper();
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index ef6fced..646ee3f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -45,6 +45,12 @@
import static android.provider.Settings.Global.ZEN_MODE_OFF;
import static android.service.notification.Condition.STATE_FALSE;
import static android.service.notification.Condition.STATE_TRUE;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT_USER;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_UNKNOWN;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
@@ -53,9 +59,6 @@
import static com.android.os.dnd.DNDProtoEnums.ROOT_CONFIG;
import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW;
import static com.android.os.dnd.DNDProtoEnums.STATE_DISALLOW;
-import static com.android.server.notification.ZenModeHelper.FROM_APP;
-import static com.android.server.notification.ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI;
-import static com.android.server.notification.ZenModeHelper.FROM_USER;
import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE;
import static com.google.common.truth.Truth.assertThat;
@@ -305,8 +308,8 @@
mZenModeHelper.writeXml(serializer, false, version, UserHandle.USER_ALL);
serializer.endDocument();
serializer.flush();
- mZenModeHelper.setConfig(new ZenModeConfig(), null, "writing xml", Process.SYSTEM_UID,
- true);
+ mZenModeHelper.setConfig(new ZenModeConfig(), null, UPDATE_ORIGIN_INIT, "writing xml",
+ Process.SYSTEM_UID);
return baos;
}
@@ -321,7 +324,8 @@
serializer.flush();
ZenModeConfig newConfig = new ZenModeConfig();
newConfig.user = userId;
- mZenModeHelper.setConfig(newConfig, null, "writing xml", Process.SYSTEM_UID, true);
+ mZenModeHelper.setConfig(newConfig, null, UPDATE_ORIGIN_INIT, "writing xml",
+ Process.SYSTEM_UID);
return baos;
}
@@ -838,7 +842,7 @@
mZenModeHelper.mConfig = null; // will evaluate config to zen mode off
for (int i = 0; i < 3; i++) {
// if zen doesn't change, zen should not reapply itself to the ringer
- mZenModeHelper.evaluateZenModeLocked("test", true);
+ mZenModeHelper.evaluateZenModeLocked(UPDATE_ORIGIN_UNKNOWN, "test", true);
}
verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
mZenModeHelper.TAG);
@@ -865,7 +869,7 @@
mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
for (int i = 0; i < 3; i++) {
// if zen doesn't change, zen should not reapply itself to the ringer
- mZenModeHelper.evaluateZenModeLocked("test", true);
+ mZenModeHelper.evaluateZenModeLocked(UPDATE_ORIGIN_UNKNOWN, "test", true);
}
verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
mZenModeHelper.TAG);
@@ -892,7 +896,7 @@
mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
for (int i = 0; i < 3; i++) {
// if zen doesn't change, zen should not reapply itself to the ringer
- mZenModeHelper.evaluateZenModeLocked("test", true);
+ mZenModeHelper.evaluateZenModeLocked(UPDATE_ORIGIN_UNKNOWN, "test", true);
}
verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
mZenModeHelper.TAG);
@@ -905,8 +909,8 @@
setupZenConfig();
// Turn manual zen mode on
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null,
- "test", CUSTOM_PKG_UID, false);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP,
+ null, "test", CUSTOM_PKG_UID);
// audio manager shouldn't do anything until the handler processes its messages
verify(mAudioManager, never()).updateRingerModeAffectedStreamsInternal();
@@ -1185,7 +1189,8 @@
List<StatsEvent> events = new LinkedList<>();
mZenModeHelper.pullRules(events);
- mZenModeHelper.removeAutomaticZenRule(CUSTOM_RULE_ID, "test", CUSTOM_PKG_UID, false);
+ mZenModeHelper.removeAutomaticZenRule(CUSTOM_RULE_ID, UPDATE_ORIGIN_APP, "test",
+ CUSTOM_PKG_UID);
assertTrue(-1
== mZenModeHelper.mRulesUidCache.getOrDefault(CUSTOM_PKG_NAME + "|" + 0, -1));
}
@@ -1240,12 +1245,14 @@
config10.user = 10;
config10.allowAlarms = true;
config10.allowMedia = true;
- mZenModeHelper.setConfig(config10, null, "writeXml", Process.SYSTEM_UID, true);
+ mZenModeHelper.setConfig(config10, null, UPDATE_ORIGIN_INIT, "writeXml",
+ Process.SYSTEM_UID);
ZenModeConfig config11 = mZenModeHelper.mConfig.copy();
config11.user = 11;
config11.allowAlarms = false;
config11.allowMedia = false;
- mZenModeHelper.setConfig(config11, null, "writeXml", Process.SYSTEM_UID, true);
+ mZenModeHelper.setConfig(config11, null, UPDATE_ORIGIN_INIT, "writeXml",
+ Process.SYSTEM_UID);
// Backup user 10 and reset values.
ByteArrayOutputStream baos = writeXmlAndPurgeForUser(null, 10);
@@ -1849,8 +1856,8 @@
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
// We need the package name to be something that's not "android" so there aren't any
// existing rules under that package.
- String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test",
- CUSTOM_PKG_UID, FROM_APP);
+ String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+ "test", CUSTOM_PKG_UID);
assertNotNull(id);
}
try {
@@ -1860,8 +1867,8 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test",
- CUSTOM_PKG_UID, FROM_APP);
+ String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+ "test", CUSTOM_PKG_UID);
fail("allowed too many rules to be created");
} catch (IllegalArgumentException e) {
// yay
@@ -1881,8 +1888,8 @@
ZenModeConfig.toScheduleConditionId(si),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test",
- CUSTOM_PKG_UID, FROM_APP);
+ String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+ "test", CUSTOM_PKG_UID);
assertNotNull(id);
}
try {
@@ -1892,8 +1899,8 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test",
- CUSTOM_PKG_UID, FROM_APP);
+ String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+ "test", CUSTOM_PKG_UID);
fail("allowed too many rules to be created");
} catch (IllegalArgumentException e) {
// yay
@@ -1913,8 +1920,8 @@
ZenModeConfig.toScheduleConditionId(si),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test",
- CUSTOM_PKG_UID, FROM_APP);
+ String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+ "test", CUSTOM_PKG_UID);
assertNotNull(id);
}
try {
@@ -1924,8 +1931,8 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test",
- CUSTOM_PKG_UID, FROM_APP);
+ String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+ "test", CUSTOM_PKG_UID);
fail("allowed too many rules to be created");
} catch (IllegalArgumentException e) {
// yay
@@ -1940,8 +1947,8 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, "test",
- Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+ String id = mZenModeHelper.addAutomaticZenRule("android", zenRule,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
assertTrue(id != null);
ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
@@ -1961,8 +1968,8 @@
new ComponentName("android", "ScheduleConditionProvider"),
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, "test",
- Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+ String id = mZenModeHelper.addAutomaticZenRule("android", zenRule,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
assertTrue(id != null);
ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
@@ -1985,11 +1992,12 @@
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test",
- CUSTOM_PKG_UID, FROM_APP);
+ String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test",
+ CUSTOM_PKG_UID);
mZenModeHelper.setAutomaticZenRuleState(zenRule.getConditionId(),
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- CUSTOM_PKG_UID, false);
+ UPDATE_ORIGIN_APP,
+ CUSTOM_PKG_UID);
ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
assertEquals(STATE_TRUE, ruleInConfig.condition.state);
@@ -2004,8 +2012,8 @@
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test",
- CUSTOM_PKG_UID, FROM_APP);
+ String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test",
+ CUSTOM_PKG_UID);
AutomaticZenRule zenRule2 = new AutomaticZenRule("NEW",
null,
@@ -2014,7 +2022,7 @@
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- mZenModeHelper.updateAutomaticZenRule(id, zenRule2, "", CUSTOM_PKG_UID, FROM_APP);
+ mZenModeHelper.updateAutomaticZenRule(id, zenRule2, UPDATE_ORIGIN_APP, "", CUSTOM_PKG_UID);
ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
assertEquals("NEW", ruleInConfig.name);
@@ -2029,15 +2037,15 @@
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test",
- CUSTOM_PKG_UID, FROM_APP);
+ String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test",
+ CUSTOM_PKG_UID);
assertTrue(id != null);
ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
assertTrue(ruleInConfig != null);
assertEquals(zenRule.getName(), ruleInConfig.name);
- mZenModeHelper.removeAutomaticZenRule(id, "test", CUSTOM_PKG_UID, false);
+ mZenModeHelper.removeAutomaticZenRule(id, UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID);
assertNull(mZenModeHelper.mConfig.automaticRules.get(id));
}
@@ -2049,16 +2057,16 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test",
- CUSTOM_PKG_UID, FROM_APP);
+ String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test",
+ CUSTOM_PKG_UID);
assertTrue(id != null);
ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
assertTrue(ruleInConfig != null);
assertEquals(zenRule.getName(), ruleInConfig.name);
- mZenModeHelper.removeAutomaticZenRules(mContext.getPackageName(), "test",
- CUSTOM_PKG_UID, false);
+ mZenModeHelper.removeAutomaticZenRules(mContext.getPackageName(), UPDATE_ORIGIN_APP, "test",
+ CUSTOM_PKG_UID);
assertNull(mZenModeHelper.mConfig.automaticRules.get(id));
}
@@ -2073,17 +2081,18 @@
new ComponentName("android", "ScheduleConditionProvider"),
sharedUri,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, "test",
- Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+ String id = mZenModeHelper.addAutomaticZenRule("android", zenRule,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
new ComponentName("android", "ScheduleConditionProvider"),
sharedUri,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id2 = mZenModeHelper.addAutomaticZenRule("android", zenRule2, "test",
- Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+ String id2 = mZenModeHelper.addAutomaticZenRule("android", zenRule2,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
Condition condition = new Condition(sharedUri, "", STATE_TRUE);
- mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition, Process.SYSTEM_UID, true);
+ mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) {
if (rule.id.equals(id)) {
@@ -2097,7 +2106,8 @@
}
condition = new Condition(sharedUri, "", STATE_FALSE);
- mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition, Process.SYSTEM_UID, true);
+ mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) {
if (rule.id.equals(id)) {
@@ -2133,7 +2143,7 @@
.setOwner(OWNER)
.setDeviceEffects(zde)
.build(),
- "reasons", 0, FROM_APP);
+ UPDATE_ORIGIN_APP, "reasons", 0);
AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
assertThat(savedRule.getDeviceEffects()).isEqualTo(
@@ -2167,7 +2177,7 @@
.setOwner(OWNER)
.setDeviceEffects(zde)
.build(),
- "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
assertThat(savedRule.getDeviceEffects()).isEqualTo(zde);
@@ -2195,7 +2205,8 @@
.setOwner(OWNER)
.setDeviceEffects(zde)
.build(),
- "reasons", 0, FROM_USER);
+ UPDATE_ORIGIN_USER,
+ "reasons", 0);
AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
assertThat(savedRule.getDeviceEffects()).isEqualTo(zde);
@@ -2212,7 +2223,7 @@
.setOwner(OWNER)
.setDeviceEffects(original)
.build(),
- "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
ZenDeviceEffects updateFromApp = new ZenDeviceEffects.Builder()
.setShouldUseNightMode(true) // Good
@@ -2223,7 +2234,7 @@
.setOwner(OWNER)
.setDeviceEffects(updateFromApp)
.build(),
- "reasons", 0, FROM_APP);
+ UPDATE_ORIGIN_APP, "reasons", 0);
AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
assertThat(savedRule.getDeviceEffects()).isEqualTo(
@@ -2244,7 +2255,7 @@
.setOwner(OWNER)
.setDeviceEffects(original)
.build(),
- "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
ZenDeviceEffects updateFromSystem = new ZenDeviceEffects.Builder()
.setShouldUseNightMode(true) // Good
@@ -2254,7 +2265,7 @@
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setDeviceEffects(updateFromSystem)
.build(),
- "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromSystem);
@@ -2271,7 +2282,7 @@
.setOwner(OWNER)
.setDeviceEffects(original)
.build(),
- "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
ZenDeviceEffects updateFromUser = new ZenDeviceEffects.Builder()
.setShouldUseNightMode(true) // Good
@@ -2281,7 +2292,7 @@
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setDeviceEffects(updateFromUser)
.build(),
- "reasons", 0, FROM_USER);
+ UPDATE_ORIGIN_USER, "reasons", 0);
AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromUser);
@@ -2293,16 +2304,16 @@
setupZenConfig();
// note that caller=null because that's how it comes in from NMS.setZenMode
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "",
- Process.SYSTEM_UID, true);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
// confirm that setting zen mode via setManualZenMode changed the zen mode correctly
assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeHelper.mZenMode);
assertEquals(true, mZenModeHelper.mConfig.manualRule.allowManualInvocation);
// and also that it works to turn it back off again
- mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "",
- Process.SYSTEM_UID, true);
+ mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+ "", null, Process.SYSTEM_UID);
assertEquals(Global.ZEN_MODE_OFF, mZenModeHelper.mZenMode);
}
@@ -2312,15 +2323,15 @@
setupZenConfig();
// note that caller=null because that's how it comes in from NMS.setZenMode
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "",
- Process.SYSTEM_UID, true);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
// confirm that setting zen mode via setManualZenMode changed the zen mode correctly
assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeHelper.mZenMode);
// and also that it works to turn it back off again
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, null, "",
- Process.SYSTEM_UID, true);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
+ null, Process.SYSTEM_UID);
assertEquals(ZEN_MODE_OFF, mZenModeHelper.mZenMode);
}
@@ -2332,13 +2343,13 @@
// Turn zen mode on (to important_interruptions)
// Need to additionally call the looper in order to finish the post-apply-config process
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "",
- Process.SYSTEM_UID, true);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
// Now turn zen mode off, but via a different package UID -- this should get registered as
// "not an action by the user" because some other app is changing zen mode
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, null, "", CUSTOM_PKG_UID,
- false);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "", null,
+ CUSTOM_PKG_UID);
// In total, this should be 2 loggable changes
assertEquals(2, mZenModeEventLogger.numLoggedChanges());
@@ -2397,17 +2408,18 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
// Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- Process.SYSTEM_UID, true);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+ Process.SYSTEM_UID);
// Event 2: "User" turns off the automatic rule (sets it to not enabled)
zenRule.setEnabled(false);
- mZenModeHelper.updateAutomaticZenRule(id, zenRule, "", Process.SYSTEM_UID,
- FROM_SYSTEM_OR_SYSTEMUI);
+ mZenModeHelper.updateAutomaticZenRule(id, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
+ Process.SYSTEM_UID);
// Add a new system rule
AutomaticZenRule systemRule = new AutomaticZenRule("systemRule",
@@ -2417,15 +2429,16 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String systemId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), systemRule,
- "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
// Event 3: turn on the system rule
mZenModeHelper.setAutomaticZenRuleState(systemId,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- Process.SYSTEM_UID, true);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// Event 4: "User" deletes the rule
- mZenModeHelper.removeAutomaticZenRule(systemId, "", Process.SYSTEM_UID, true);
+ mZenModeHelper.removeAutomaticZenRule(systemId, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
+ Process.SYSTEM_UID);
// In total, this represents 4 events
assertEquals(4, mZenModeEventLogger.numLoggedChanges());
@@ -2486,26 +2499,26 @@
setupZenConfig();
// First just turn zen mode on
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "",
- Process.SYSTEM_UID, true);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
// Now change the policy slightly; want to confirm that this'll be reflected in the logs
ZenModeConfig newConfig = mZenModeHelper.mConfig.copy();
newConfig.allowAlarms = true;
newConfig.allowRepeatCallers = false;
- mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), Process.SYSTEM_UID,
- true);
+ mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// Turn zen mode off; we want to make sure policy changes do not get logged when zen mode
// is off.
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, null, "",
- Process.SYSTEM_UID, true);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
+ null, Process.SYSTEM_UID);
// Change the policy again
newConfig.allowMessages = false;
newConfig.allowRepeatCallers = true;
- mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), Process.SYSTEM_UID,
- true);
+ mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// Total events: we only expect ones for turning on, changing policy, and turning off
assertEquals(3, mZenModeEventLogger.numLoggedChanges());
@@ -2548,8 +2561,8 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
- Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+ String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
// Rule 2, same as rule 1
AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
@@ -2558,8 +2571,8 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, "test",
- Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+ String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
// Rule 3, has stricter settings than the default settings
ZenModeConfig ruleConfig = mZenModeHelper.mConfig.copy();
@@ -2572,28 +2585,28 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
ruleConfig.toZenPolicy(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id3 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule3, "test",
- Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+ String id3 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule3,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
// First: turn on rule 1
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- Process.SYSTEM_UID, true);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// Second: turn on rule 2
mZenModeHelper.setAutomaticZenRuleState(id2,
new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
- Process.SYSTEM_UID, true);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// Third: turn on rule 3
mZenModeHelper.setAutomaticZenRuleState(id3,
new Condition(zenRule3.getConditionId(), "", STATE_TRUE),
- Process.SYSTEM_UID, true);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// Fourth: Turn *off* rule 2
mZenModeHelper.setAutomaticZenRuleState(id2,
new Condition(zenRule2.getConditionId(), "", STATE_FALSE),
- Process.SYSTEM_UID, true);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// This should result in a total of four events
assertEquals(4, mZenModeEventLogger.numLoggedChanges());
@@ -2649,7 +2662,7 @@
// Artificially turn zen mode "on". Re-evaluating zen mode should cause it to turn back off
// given that we don't have any zen rules active.
mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- mZenModeHelper.evaluateZenModeLocked("test", true);
+ mZenModeHelper.evaluateZenModeLocked(UPDATE_ORIGIN_UNKNOWN, "test", true);
// Check that the change actually took: zen mode should be off now
assertEquals(ZEN_MODE_OFF, mZenModeHelper.mZenMode);
@@ -2672,8 +2685,8 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
- Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+ String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
// Rule 2, same as rule 1 but owned by the system
AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
@@ -2682,37 +2695,37 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, "test",
- Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+ String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
// Turn on rule 1; call looks like it's from the system. Because setting a condition is
// typically an automatic (non-user-initiated) action, expect the calling UID to be
// re-evaluated to the one associat.d with CUSTOM_PKG_NAME.
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- Process.SYSTEM_UID, true);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// Second: turn on rule 2. This is a system-owned rule and the UID should not be modified
// (nor even looked up; the mock PackageManager won't handle "android" as input).
mZenModeHelper.setAutomaticZenRuleState(id2,
new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
- Process.SYSTEM_UID, true);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// Disable rule 1. Because this looks like a user action, the UID should not be modified
// from the system-provided one.
zenRule.setEnabled(false);
- mZenModeHelper.updateAutomaticZenRule(id, zenRule, "", Process.SYSTEM_UID,
- FROM_SYSTEM_OR_SYSTEMUI);
+ mZenModeHelper.updateAutomaticZenRule(id, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
+ Process.SYSTEM_UID);
// Add a manual rule. Any manual rule changes should not get calling uids reassigned.
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "",
- CUSTOM_PKG_UID, false);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP,
+ "", null, CUSTOM_PKG_UID);
// Change rule 2's condition, but from some other UID. Since it doesn't look like it's from
// the system, we keep the UID info.
mZenModeHelper.setAutomaticZenRuleState(id2,
new Condition(zenRule2.getConditionId(), "", STATE_FALSE),
- 12345, false);
+ UPDATE_ORIGIN_APP, 12345);
// That was 5 events total
assertEquals(5, mZenModeEventLogger.numLoggedChanges());
@@ -2767,26 +2780,26 @@
// Turn on zen mode with a manual rule with an enabler set. This should *not* count
// as a user action, and *should* get its UID reassigned.
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- CUSTOM_PKG_NAME, "", Process.SYSTEM_UID, true);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", CUSTOM_PKG_NAME, Process.SYSTEM_UID);
// Now change apps bypassing to true
ZenModeConfig newConfig = mZenModeHelper.mConfig.copy();
newConfig.areChannelsBypassingDnd = true;
- mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), Process.SYSTEM_UID,
- true);
+ mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// and then back to false, all without changing anything else
newConfig.areChannelsBypassingDnd = false;
- mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), Process.SYSTEM_UID,
- true);
+ mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// Turn off manual mode, call from a package: don't reset UID even though enabler is set
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null,
- CUSTOM_PKG_NAME, "", 12345, false);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "",
+ CUSTOM_PKG_NAME, 12345);
// And likewise when turning it back on again
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- CUSTOM_PKG_NAME, "", 12345, false);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP,
+ "", CUSTOM_PKG_NAME, 12345);
// These are 5 events in total.
assertEquals(5, mZenModeEventLogger.numLoggedChanges());
@@ -2831,15 +2844,15 @@
setupZenConfig();
// First just turn zen mode on
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "",
- Process.SYSTEM_UID, true);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
// Now change only the channels part of the policy; want to confirm that this'll be
// reflected in the logs
ZenModeConfig newConfig = mZenModeHelper.mConfig.copy();
newConfig.allowPriorityChannels = false;
- mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), Process.SYSTEM_UID,
- true);
+ mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// Total events: one for turning on, one for changing policy
assertThat(mZenModeEventLogger.numLoggedChanges()).isEqualTo(2);
@@ -2881,13 +2894,13 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
- Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+ String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
// enable the rule
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- Process.SYSTEM_UID, true);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// inspect the consolidated policy. Based on setupZenConfig() values.
assertFalse(mZenModeHelper.mConsolidatedPolicy.allowAlarms());
@@ -2924,13 +2937,13 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
customPolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
- Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+ String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
// enable the rule; this will update the consolidated policy
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- Process.SYSTEM_UID, true);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// since this is the only active rule, the consolidated policy should match the custom
// policy for every field specified, and take default values for unspecified things
@@ -2960,13 +2973,13 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
- Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+ String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
// enable rule 1
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- Process.SYSTEM_UID, true);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// custom policy for rule 2
ZenPolicy customPolicy = new ZenPolicy.Builder()
@@ -2985,12 +2998,12 @@
customPolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
- "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
// enable rule 2; this will update the consolidated policy
mZenModeHelper.setAutomaticZenRuleState(id2,
new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
- Process.SYSTEM_UID, true);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// now both rules should be on, and the consolidated policy should reflect the most
// restrictive option of each of the two
@@ -3022,13 +3035,13 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
customPolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
- Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+ String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
// enable the rule; this will update the consolidated policy
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- Process.SYSTEM_UID, true);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// confirm that channels make it through
assertTrue(mZenModeHelper.mConsolidatedPolicy.allowPriorityChannels());
@@ -3045,12 +3058,12 @@
strictPolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
- "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
// enable rule 2; this will update the consolidated policy
mZenModeHelper.setAutomaticZenRuleState(id2,
new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
- Process.SYSTEM_UID, true);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// rule 2 should override rule 1
assertFalse(mZenModeHelper.mConsolidatedPolicy.allowPriorityChannels());
@@ -3121,7 +3134,7 @@
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
- mZenModeHelper.populateZenRule(OWNER.getPackageName(), azr, rule, true, FROM_APP);
+ mZenModeHelper.populateZenRule(OWNER.getPackageName(), azr, rule, UPDATE_ORIGIN_APP, true);
assertEquals(NAME, rule.name);
assertEquals(OWNER, rule.component);
@@ -3149,7 +3162,7 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+ zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
CountDownLatch latch = new CountDownLatch(1);
final int[] actualStatus = new int[1];
@@ -3165,8 +3178,8 @@
mZenModeHelper.addCallback(callback);
zenRule.setEnabled(false);
- mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID,
- FROM_SYSTEM_OR_SYSTEMUI);
+ mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+ "", Process.SYSTEM_UID);
assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
assertEquals(AUTOMATIC_RULE_STATUS_DISABLED, actualStatus[0]);
@@ -3184,7 +3197,7 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, false);
final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+ zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
CountDownLatch latch = new CountDownLatch(1);
final int[] actualStatus = new int[1];
@@ -3200,8 +3213,8 @@
mZenModeHelper.addCallback(callback);
zenRule.setEnabled(true);
- mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID,
- FROM_SYSTEM_OR_SYSTEMUI);
+ mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+ "", Process.SYSTEM_UID);
assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
assertEquals(AUTOMATIC_RULE_STATUS_ENABLED, actualStatus[0]);
@@ -3220,7 +3233,7 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+ zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
CountDownLatch latch = new CountDownLatch(1);
final int[] actualStatus = new int[1];
@@ -3237,7 +3250,7 @@
mZenModeHelper.setAutomaticZenRuleState(createdId,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- Process.SYSTEM_UID, true);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
assertEquals(AUTOMATIC_RULE_STATUS_ACTIVATED, actualStatus[0]);
@@ -3256,7 +3269,7 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+ zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
CountDownLatch latch = new CountDownLatch(1);
final int[] actualStatus = new int[2];
@@ -3274,10 +3287,10 @@
mZenModeHelper.setAutomaticZenRuleState(createdId,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- Process.SYSTEM_UID, true);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
- mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "",
- Process.SYSTEM_UID, true);
+ mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+ null, "", Process.SYSTEM_UID);
assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
assertEquals(AUTOMATIC_RULE_STATUS_DEACTIVATED, actualStatus[1]);
@@ -3296,7 +3309,7 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+ zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
CountDownLatch latch = new CountDownLatch(1);
final int[] actualStatus = new int[2];
@@ -3314,11 +3327,11 @@
mZenModeHelper.setAutomaticZenRuleState(createdId,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- Process.SYSTEM_UID, true);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
mZenModeHelper.setAutomaticZenRuleState(createdId,
new Condition(zenRule.getConditionId(), "", STATE_FALSE),
- Process.SYSTEM_UID, true);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
assertEquals(AUTOMATIC_RULE_STATUS_DEACTIVATED, actualStatus[1]);
@@ -3336,21 +3349,21 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI);
+ zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
// Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE
mZenModeHelper.setAutomaticZenRuleState(createdId,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- Process.SYSTEM_UID, true);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// Event 2: Snooze rule by turning off DND
- mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "",
- Process.SYSTEM_UID, true);
+ mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+ "", null, Process.SYSTEM_UID);
// Event 3: "User" turns off the automatic rule (sets it to not enabled)
zenRule.setEnabled(false);
- mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID,
- FROM_SYSTEM_OR_SYSTEMUI);
+ mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+ "", Process.SYSTEM_UID);
assertEquals(false, mZenModeHelper.mConfig.automaticRules.get(createdId).snoozing);
}
@@ -3359,18 +3372,20 @@
public void testDeviceEffects_applied() {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+ verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT));
ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
.setShouldSuppressAmbientDisplay(true)
.setShouldDimWallpaper(true)
.build();
String ruleId = addRuleWithEffects(effects);
- verify(mDeviceEffectsApplier, never()).apply(any());
+ verifyNoMoreInteractions(mDeviceEffectsApplier);
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false);
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
- verify(mDeviceEffectsApplier).apply(eq(effects));
+ verify(mDeviceEffectsApplier).apply(eq(effects), eq(UPDATE_ORIGIN_APP));
}
@Test
@@ -3380,31 +3395,66 @@
ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build();
String ruleId = addRuleWithEffects(zde);
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false);
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
- verify(mDeviceEffectsApplier).apply(eq(zde));
+ verify(mDeviceEffectsApplier).apply(eq(zde), eq(UPDATE_ORIGIN_APP));
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_FALSE, CUSTOM_PKG_UID, false);
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_FALSE, UPDATE_ORIGIN_APP,
+ CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
- verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS));
+ verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_APP));
}
@Test
+ public void testDeviceEffects_changeToConsolidatedEffects_applied() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+ verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT));
+
+ String ruleId = addRuleWithEffects(
+ new ZenDeviceEffects.Builder().setShouldDisplayGrayscale(true).build());
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ CUSTOM_PKG_UID);
+ mTestableLooper.processAllMessages();
+ verify(mDeviceEffectsApplier).apply(
+ eq(new ZenDeviceEffects.Builder()
+ .setShouldDisplayGrayscale(true)
+ .build()),
+ eq(UPDATE_ORIGIN_APP));
+
+ // Now create and activate a second rule that adds more effects.
+ String secondRuleId = addRuleWithEffects(
+ new ZenDeviceEffects.Builder().setShouldDimWallpaper(true).build());
+ mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ CUSTOM_PKG_UID);
+ mTestableLooper.processAllMessages();
+
+ verify(mDeviceEffectsApplier).apply(
+ eq(new ZenDeviceEffects.Builder()
+ .setShouldDisplayGrayscale(true)
+ .setShouldDimWallpaper(true)
+ .build()),
+ eq(UPDATE_ORIGIN_APP));
+ }
+ @Test
public void testDeviceEffects_noChangeToConsolidatedEffects_notApplied() {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+ verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT));
ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build();
String ruleId = addRuleWithEffects(zde);
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false);
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
- verify(mDeviceEffectsApplier).apply(eq(zde));
+ verify(mDeviceEffectsApplier).apply(eq(zde), eq(UPDATE_ORIGIN_APP));
// Now create and activate a second rule that doesn't add any more effects.
String secondRuleId = addRuleWithEffects(zde);
- mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, CUSTOM_PKG_UID,
- false);
+ mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
verifyNoMoreInteractions(mDeviceEffectsApplier);
@@ -3416,21 +3466,50 @@
ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build();
String ruleId = addRuleWithEffects(zde);
- verify(mDeviceEffectsApplier, never()).apply(any());
+ verify(mDeviceEffectsApplier, never()).apply(any(), anyInt());
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false);
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
- verify(mDeviceEffectsApplier, never()).apply(any());
+ verify(mDeviceEffectsApplier, never()).apply(any(), anyInt());
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
- verify(mDeviceEffectsApplier).apply(eq(zde));
+ verify(mDeviceEffectsApplier).apply(eq(zde), eq(UPDATE_ORIGIN_INIT));
+ }
+
+ @Test
+ public void testDeviceEffects_onUserSwitch_appliedImmediately() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+ verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT));
+
+ // Initialize default configurations (default rules) for both users.
+ mZenModeHelper.onUserSwitched(1);
+ mZenModeHelper.onUserSwitched(2);
+
+ // Current user is now 2. Tweak a rule for user 1 so it's active and has effects.
+ ZenRule user1Rule = mZenModeHelper.mConfigs.get(1).automaticRules.valueAt(0);
+ user1Rule.enabled = true;
+ user1Rule.condition = new Condition(user1Rule.conditionId, "on", STATE_TRUE);
+ user1Rule.zenDeviceEffects = new ZenDeviceEffects.Builder()
+ .setShouldDimWallpaper(true)
+ .setShouldUseNightMode(true)
+ .build();
+ verifyNoMoreInteractions(mDeviceEffectsApplier);
+
+ mZenModeHelper.onUserSwitched(1);
+ mTestableLooper.processAllMessages();
+
+ verify(mDeviceEffectsApplier).apply(eq(user1Rule.zenDeviceEffects),
+ eq(UPDATE_ORIGIN_INIT_USER));
}
private String addRuleWithEffects(ZenDeviceEffects effects) {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
.setDeviceEffects(effects)
.build();
- return mZenModeHelper.addAutomaticZenRule("pkg", rule, "", CUSTOM_PKG_UID, FROM_APP);
+ return mZenModeHelper.addAutomaticZenRule("pkg", rule, UPDATE_ORIGIN_APP, "",
+ CUSTOM_PKG_UID);
}
@Test
@@ -3455,7 +3534,7 @@
mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
ZEN_MODE_IMPORTANT_INTERRUPTIONS);
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, "test", "test", 0, true);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "test", "test", 0);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
@@ -3505,7 +3584,7 @@
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isFalse();
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, "test", "test", 0, true);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "test", "test", 0);
assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isTrue();
mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index c3074bb..ef19791 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -99,11 +99,6 @@
android:theme="@style/WhiteBackgroundTheme"
android:exported="true"/>
- <activity android:name="com.android.server.wm.TrustedPresentationCallbackTest$TestActivity"
- android:exported="true"
- android:showWhenLocked="true"
- android:turnScreenOn="true" />
-
<activity android:name="android.app.Activity"
android:exported="true"
android:showWhenLocked="true"
diff --git a/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java b/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java
new file mode 100644
index 0000000..44b69f1
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.hardware.display.DeviceProductInfo.CONNECTION_TO_SINK_UNKNOWN;
+import static android.view.RoundedCorner.POSITION_TOP_LEFT;
+import static android.view.RoundedCorners.NO_ROUNDED_CORNERS;
+
+import static com.android.server.wm.DeferredDisplayUpdater.DEFERRABLE_FIELDS;
+import static com.android.server.wm.DeferredDisplayUpdater.DIFF_NOT_WM_DEFERRABLE;
+import static com.android.server.wm.DeferredDisplayUpdater.DIFF_WM_DEFERRABLE;
+import static com.android.server.wm.DeferredDisplayUpdater.calculateDisplayInfoDiff;
+import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.annotation.NonNull;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.hardware.display.DeviceProductInfo;
+import android.platform.test.annotations.Presubmit;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.DisplayAddress;
+import android.view.DisplayCutout;
+import android.view.DisplayInfo;
+import android.view.DisplayShape;
+import android.view.RoundedCorner;
+import android.view.RoundedCorners;
+import android.view.SurfaceControl.RefreshRateRange;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Consumer;
+
+/**
+ * Build/Install/Run:
+ * atest WmTests:DeferredDisplayUpdaterDiffTest
+ */
+@SmallTest
+@Presubmit
+public class DeferredDisplayUpdaterDiffTest {
+
+ private static final Set<String> IGNORED_FIELDS = new HashSet<>(Arrays.asList(
+ "name" // human-readable name is ignored in equals() checks
+ ));
+
+ private static final DisplayInfo EMPTY = new DisplayInfo();
+
+ @Test
+ public void testCalculateDisplayInfoDiff_allDifferent_returnsChanges() {
+ final DisplayInfo first = new DisplayInfo();
+ final DisplayInfo second = new DisplayInfo();
+ makeAllFieldsDifferent(first, second);
+
+ int diff = calculateDisplayInfoDiff(first, second);
+
+ assertWithMessage("Expected to receive a non-zero difference when "
+ + "there are changes in all fields of DisplayInfo\n"
+ + "Make sure that you have updated calculateDisplayInfoDiff function after "
+ + "changing DisplayInfo fields").that(diff).isGreaterThan(0);
+ }
+
+ @Test
+ public void testCalculateDisplayInfoDiff_forEveryDifferentField_returnsChanges() {
+ generateWithSingleDifferentField((first, second, field) -> {
+ int diff = calculateDisplayInfoDiff(first, second);
+
+ assertWithMessage("Expected to receive a non-zero difference when "
+ + "there are changes in " + field + "\n"
+ + "Make sure that you have updated calculateDisplayInfoDiff function after "
+ + "changing DisplayInfo fields").that(diff).isGreaterThan(0);
+ });
+ }
+
+ @Test
+ public void testCalculateDisplayInfoDiff_forEveryDifferentField_returnsMatchingChange() {
+ generateWithSingleDifferentField((first, second, field) -> {
+ boolean hasDeferrableFieldChange = hasDeferrableFieldChange(first, second);
+ int expectedDiff =
+ hasDeferrableFieldChange ? DIFF_WM_DEFERRABLE : DIFF_NOT_WM_DEFERRABLE;
+
+ int diff = calculateDisplayInfoDiff(first, second);
+
+ assertWithMessage("Expected to have diff = " + expectedDiff
+ + ", for field = " + field + "\n"
+ + "Make sure that you have updated calculateDisplayInfoDiff function after "
+ + "changing DisplayInfo fields").that(
+ diff).isEqualTo(expectedDiff);
+ });
+ }
+
+ /**
+ * Sets each field of the objects to different values using reflection
+ */
+ private static void makeAllFieldsDifferent(@NonNull DisplayInfo first,
+ @NonNull DisplayInfo second) {
+ forEachDisplayInfoField(field -> {
+ try {
+ setDifferentFieldValues(first, second, field);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
+ private static boolean hasDeferrableFieldChange(@NonNull DisplayInfo first,
+ @NonNull DisplayInfo second) {
+ final DisplayInfo firstDeferrableFieldsOnly = new DisplayInfo();
+ final DisplayInfo secondDeferrableFieldsOnly = new DisplayInfo();
+
+ copyDisplayInfoFields(/* out= */ firstDeferrableFieldsOnly, /* base= */
+ EMPTY, /* override= */ first, DEFERRABLE_FIELDS);
+ copyDisplayInfoFields(/* out= */ secondDeferrableFieldsOnly, /* base= */
+ EMPTY, /* override= */ second, DEFERRABLE_FIELDS);
+
+ return !firstDeferrableFieldsOnly.equals(secondDeferrableFieldsOnly);
+ }
+
+ /**
+ * Creates pairs of DisplayInfos where only one field is different, the callback is called for
+ * each field
+ */
+ private static void generateWithSingleDifferentField(DisplayInfoConsumer consumer) {
+ forEachDisplayInfoField(field -> {
+ final DisplayInfo first = new DisplayInfo();
+ final DisplayInfo second = new DisplayInfo();
+
+ try {
+ setDifferentFieldValues(first, second, field);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+
+ consumer.consume(first, second, field);
+ });
+ }
+
+ private static void setDifferentFieldValues(@NonNull DisplayInfo first,
+ @NonNull DisplayInfo second,
+ @NonNull Field field) throws IllegalAccessException {
+ final Class<?> type = field.getType();
+ if (type.equals(int.class)) {
+ field.setInt(first, 1);
+ field.setInt(second, 2);
+ } else if (type.equals(double.class)) {
+ field.setDouble(first, 1.0);
+ field.setDouble(second, 2.0);
+ } else if (type.equals(short.class)) {
+ field.setShort(first, (short) 1);
+ field.setShort(second, (short) 2);
+ } else if (type.equals(long.class)) {
+ field.setLong(first, 1L);
+ field.setLong(second, 2L);
+ } else if (type.equals(char.class)) {
+ field.setChar(first, 'a');
+ field.setChar(second, 'b');
+ } else if (type.equals(byte.class)) {
+ field.setByte(first, (byte) 1);
+ field.setByte(second, (byte) 2);
+ } else if (type.equals(float.class)) {
+ field.setFloat(first, 1.0f);
+ field.setFloat(second, 2.0f);
+ } else if (type == boolean.class) {
+ field.setBoolean(first, true);
+ field.setBoolean(second, false);
+ } else if (type.equals(String.class)) {
+ field.set(first, "one");
+ field.set(second, "two");
+ } else if (type.equals(DisplayAddress.class)) {
+ field.set(first, DisplayAddress.fromPhysicalDisplayId(0));
+ field.set(second, DisplayAddress.fromPhysicalDisplayId(1));
+ } else if (type.equals(DeviceProductInfo.class)) {
+ field.set(first, new DeviceProductInfo("name", "pnp_id", "product_id1", 2023,
+ CONNECTION_TO_SINK_UNKNOWN));
+ field.set(second, new DeviceProductInfo("name", "pnp_id", "product_id2", 2023,
+ CONNECTION_TO_SINK_UNKNOWN));
+ } else if (type.equals(DisplayCutout.class)) {
+ field.set(first,
+ new DisplayCutout(Insets.NONE, new Rect(0, 0, 100, 100), null, null,
+ null));
+ field.set(second,
+ new DisplayCutout(Insets.NONE, new Rect(0, 0, 200, 200), null, null,
+ null));
+ } else if (type.equals(RoundedCorners.class)) {
+ field.set(first, NO_ROUNDED_CORNERS);
+
+ final RoundedCorners other = new RoundedCorners(NO_ROUNDED_CORNERS);
+ other.setRoundedCorner(POSITION_TOP_LEFT,
+ new RoundedCorner(POSITION_TOP_LEFT, 1, 2, 3));
+ field.set(second, other);
+ } else if (type.equals(DisplayShape.class)) {
+ field.set(first, DisplayShape.createDefaultDisplayShape(100, 200, false));
+ field.set(second, DisplayShape.createDefaultDisplayShape(50, 100, false));
+ } else if (type.equals(RefreshRateRange.class)) {
+ field.set(first, new RefreshRateRange(0, 100));
+ field.set(second, new RefreshRateRange(20, 80));
+ } else if (type.equals(Display.HdrCapabilities.class)) {
+ field.set(first, new Display.HdrCapabilities(new int[]{0}, 100, 50, 25));
+ field.set(second, new Display.HdrCapabilities(new int[]{1}, 100, 50, 25));
+ } else if (type.equals(SparseArray.class)
+ && ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0].equals(
+ RefreshRateRange.class)) {
+ final SparseArray<RefreshRateRange> array1 = new SparseArray<>();
+ array1.set(0, new RefreshRateRange(0, 100));
+ final SparseArray<RefreshRateRange> array2 = new SparseArray<>();
+ array2.set(0, new RefreshRateRange(20, 80));
+ field.set(first, array1);
+ field.set(second, array2);
+ } else if (type.isArray() && type.getComponentType().equals(int.class)) {
+ field.set(first, new int[]{0});
+ field.set(second, new int[]{1});
+ } else if (type.isArray() && type.getComponentType().equals(Display.Mode.class)) {
+ field.set(first, new Display.Mode[]{new Display.Mode(100, 200, 300)});
+ field.set(second, new Display.Mode[]{new Display.Mode(10, 20, 30)});
+ } else {
+ throw new IllegalArgumentException("Field " + field
+ + " is not supported by this test, please add implementation of setting "
+ + "different values for this field");
+ }
+ }
+
+ private interface DisplayInfoConsumer {
+ void consume(DisplayInfo first, DisplayInfo second, Field field);
+ }
+
+ /**
+ * Iterates over every non-static field of DisplayInfo class except IGNORED_FIELDS
+ */
+ private static void forEachDisplayInfoField(Consumer<Field> consumer) {
+ for (Field field : DisplayInfo.class.getDeclaredFields()) {
+ field.setAccessible(true);
+
+ if (Modifier.isStatic(field.getModifiers())) {
+ continue;
+ }
+
+ if (IGNORED_FIELDS.contains(field.getName())) {
+ continue;
+ }
+
+ consumer.accept(field);
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
new file mode 100644
index 0000000..dfa595c
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+import android.view.DisplayInfo;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.wm.TransitionController.OnStartCollect;
+import com.android.window.flags.Flags;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+/**
+ * Tests for the {@link DisplayContent} class when FLAG_DEFER_DISPLAY_UPDATES is enabled.
+ *
+ * Build/Install/Run:
+ * atest WmTests:DisplayContentDeferredUpdateTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
+
+ @Override
+ protected void onBeforeSystemServicesCreated() {
+ // Set other flags to their default values
+ mSetFlagsRule.initAllFlagsToReleaseConfigDefault();
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_DEFER_DISPLAY_UPDATES);
+ }
+
+ @Before
+ public void before() {
+ mockTransitionsController(/* enabled= */ true);
+ mockRemoteDisplayChangeController();
+ }
+
+ @Test
+ public void testUpdate_deferrableFieldChangedTransitionStarted_deferrableFieldUpdated() {
+ performInitialDisplayUpdate();
+
+ givenDisplayInfo(/* uniqueId= */ "old");
+ Runnable onUpdated = mock(Runnable.class);
+ mDisplayContent.requestDisplayUpdate(onUpdated);
+
+ // Emulate that collection has started
+ captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true);
+ verify(onUpdated).run();
+ clearInvocations(mDisplayContent.mTransitionController, onUpdated);
+
+ givenDisplayInfo(/* uniqueId= */ "new");
+ mDisplayContent.requestDisplayUpdate(onUpdated);
+ captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true);
+ verify(onUpdated).run();
+ assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("new");
+ }
+
+ @Test
+ public void testUpdate_nonDeferrableUpdateAndTransitionDeferred_nonDeferrableFieldUpdated() {
+ performInitialDisplayUpdate();
+
+ // Update only color mode (non-deferrable field) and keep the same unique id
+ givenDisplayInfo(/* uniqueId= */ "initial_unique_id", /* colorMode= */ 123);
+ Runnable onUpdated = mock(Runnable.class);
+ mDisplayContent.requestDisplayUpdate(onUpdated);
+
+ verify(onUpdated).run();
+ assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(123);
+ }
+
+ @Test
+ public void testUpdate_nonDeferrableUpdateTwiceAndTransitionDeferred_fieldHasLatestValue() {
+ performInitialDisplayUpdate();
+
+ // Update only color mode (non-deferrable field) and keep the same unique id
+ givenDisplayInfo(/* uniqueId= */ "initial_unique_id", /* colorMode= */ 123);
+ mDisplayContent.requestDisplayUpdate(mock(Runnable.class));
+
+ assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(123);
+ assertThat(mDisplayContent.getDisplayInfo().uniqueId)
+ .isEqualTo("initial_unique_id");
+
+ // Update unique id (deferrable field), keep the same color mode,
+ // this update should be deferred
+ givenDisplayInfo(/* uniqueId= */ "new_unique_id", /* colorMode= */ 123);
+ mDisplayContent.requestDisplayUpdate(mock(Runnable.class));
+
+ assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(123);
+ assertThat(mDisplayContent.getDisplayInfo().uniqueId)
+ .isEqualTo("initial_unique_id");
+
+ // Update color mode again and keep the same unique id, color mode update
+ // should not be deferred, unique id update is still deferred as transition
+ // has not started collecting yet
+ givenDisplayInfo(/* uniqueId= */ "new_unique_id", /* colorMode= */ 456);
+ Runnable onUpdated = mock(Runnable.class);
+ mDisplayContent.requestDisplayUpdate(onUpdated);
+
+ assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(456);
+ assertThat(mDisplayContent.getDisplayInfo().uniqueId)
+ .isEqualTo("initial_unique_id");
+
+ // Mark transition as started collected, so pending changes are applied
+ captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true);
+
+ // Verify that all fields have the latest values
+ verify(onUpdated).run();
+ assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(456);
+ assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("new_unique_id");
+ }
+
+ @Test
+ public void testUpdate_deferrableFieldUpdatedTransitionPending_fieldNotUpdated() {
+ performInitialDisplayUpdate();
+ givenDisplayInfo(/* uniqueId= */ "old");
+ Runnable onUpdated = mock(Runnable.class);
+ mDisplayContent.requestDisplayUpdate(onUpdated);
+ captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true);
+ verify(onUpdated).run();
+ clearInvocations(mDisplayContent.mTransitionController, onUpdated);
+
+ givenDisplayInfo(/* uniqueId= */ "new");
+ mDisplayContent.requestDisplayUpdate(onUpdated);
+
+ captureStartTransitionCollection(); // do not continue by not starting the collection
+ verify(onUpdated, never()).run();
+ assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("old");
+ }
+
+ @Test
+ public void testTwoDisplayUpdates_transitionStarted_displayUpdated() {
+ performInitialDisplayUpdate();
+ givenDisplayInfo(/* uniqueId= */ "old");
+ Runnable onUpdated = mock(Runnable.class);
+ mDisplayContent.requestDisplayUpdate(onUpdated);
+ captureStartTransitionCollection().getValue()
+ .onCollectStarted(/* deferred= */ true);
+ verify(onUpdated).run();
+ clearInvocations(mDisplayContent.mTransitionController, onUpdated);
+
+ // Perform two display updates while WM is 'busy'
+ givenDisplayInfo(/* uniqueId= */ "new1");
+ Runnable onUpdated1 = mock(Runnable.class);
+ mDisplayContent.requestDisplayUpdate(onUpdated1);
+ givenDisplayInfo(/* uniqueId= */ "new2");
+ Runnable onUpdated2 = mock(Runnable.class);
+ mDisplayContent.requestDisplayUpdate(onUpdated2);
+
+ // Continue with the first update
+ captureStartTransitionCollection().getAllValues().get(0)
+ .onCollectStarted(/* deferred= */ true);
+ verify(onUpdated1).run();
+ verify(onUpdated2, never()).run();
+ assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("new1");
+
+ // Continue with the second update
+ captureStartTransitionCollection().getAllValues().get(1)
+ .onCollectStarted(/* deferred= */ true);
+ verify(onUpdated2).run();
+ assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("new2");
+ }
+
+ private void mockTransitionsController(boolean enabled) {
+ spyOn(mDisplayContent.mTransitionController);
+ when(mDisplayContent.mTransitionController.isShellTransitionsEnabled()).thenReturn(enabled);
+ doReturn(true).when(mDisplayContent.mTransitionController).startCollectOrQueue(any(),
+ any());
+ }
+
+ private void mockRemoteDisplayChangeController() {
+ spyOn(mDisplayContent.mRemoteDisplayChangeController);
+ doReturn(true).when(mDisplayContent.mRemoteDisplayChangeController)
+ .performRemoteDisplayChange(anyInt(), anyInt(), any(), any());
+ }
+
+ private ArgumentCaptor<OnStartCollect> captureStartTransitionCollection() {
+ ArgumentCaptor<OnStartCollect> callbackCaptor =
+ ArgumentCaptor.forClass(OnStartCollect.class);
+ verify(mDisplayContent.mTransitionController, atLeast(1)).startCollectOrQueue(any(),
+ callbackCaptor.capture());
+ return callbackCaptor;
+ }
+
+ private void givenDisplayInfo(String uniqueId) {
+ givenDisplayInfo(uniqueId, /* colorMode= */ 0);
+ }
+
+ private void givenDisplayInfo(String uniqueId, int colorMode) {
+ spyOn(mDisplayContent.mDisplay);
+ doAnswer(invocation -> {
+ DisplayInfo info = invocation.getArgument(0);
+ info.uniqueId = uniqueId;
+ info.colorMode = colorMode;
+ return null;
+ }).when(mDisplayContent.mDisplay).getDisplayInfo(any());
+ }
+
+ private void performInitialDisplayUpdate() {
+ givenDisplayInfo(/* uniqueId= */ "initial_unique_id", /* colorMode= */ 0);
+ Runnable onUpdated = mock(Runnable.class);
+ mDisplayContent.requestDisplayUpdate(onUpdated);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
index c0a90b2..e77c14a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
@@ -19,10 +19,13 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyFloat;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.window.flags.Flags.explicitRefreshRateHints;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -62,9 +65,11 @@
private static final FrameRateVote FRAME_RATE_VOTE_NONE = new FrameRateVote();
private static final FrameRateVote FRAME_RATE_VOTE_60_EXACT =
- new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+ new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_EXACT,
+ SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
private static final FrameRateVote FRAME_RATE_VOTE_60_PREFERRED =
- new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+ new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+ SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
WindowState createWindow(String name) {
WindowState window = createWindow(null, TYPE_APPLICATION, name);
@@ -110,6 +115,8 @@
any(SurfaceControl.class), anyInt());
verify(appWindow.getPendingTransaction(), never()).setFrameRate(
any(SurfaceControl.class), anyInt(), anyInt(), anyInt());
+ verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+ any(SurfaceControl.class), anyInt());
}
@Test
@@ -140,6 +147,8 @@
appWindow.getSurfaceControl(), 1);
verify(appWindow.getPendingTransaction(), never()).setFrameRate(
any(SurfaceControl.class), anyInt(), anyInt(), anyInt());
+ verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+ any(SurfaceControl.class), anyInt());
}
@Test
@@ -175,8 +184,17 @@
appWindow.getSurfaceControl(), 0);
verify(appWindow.getPendingTransaction(), times(2)).setFrameRateSelectionPriority(
appWindow.getSurfaceControl(), 1);
- verify(appWindow.getPendingTransaction(), never()).setFrameRate(
- any(SurfaceControl.class), anyInt(), anyInt(), anyInt());
+ verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
+ eq(appWindow.getSurfaceControl()), anyFloat(),
+ eq(Surface.FRAME_RATE_COMPATIBILITY_EXACT), eq(Surface.CHANGE_FRAME_RATE_ALWAYS));
+ if (explicitRefreshRateHints()) {
+ verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+ appWindow.getSurfaceControl(),
+ SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
+ } else {
+ verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+ any(SurfaceControl.class), anyInt());
+ }
}
@Test
@@ -202,8 +220,17 @@
appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
appWindow.getSurfaceControl(), 2);
- verify(appWindow.getPendingTransaction(), never()).setFrameRate(
- any(SurfaceControl.class), anyInt(), anyInt(), anyInt());
+ verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
+ eq(appWindow.getSurfaceControl()), anyFloat(),
+ eq(Surface.FRAME_RATE_COMPATIBILITY_EXACT), eq(Surface.CHANGE_FRAME_RATE_ALWAYS));
+ if (explicitRefreshRateHints()) {
+ verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+ appWindow.getSurfaceControl(),
+ SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
+ } else {
+ verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+ any(SurfaceControl.class), anyInt());
+ }
}
@Test
@@ -229,6 +256,8 @@
appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
verify(appWindow.getPendingTransaction(), never()).setFrameRate(
any(SurfaceControl.class), anyInt(), anyInt(), anyInt());
+ verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+ any(SurfaceControl.class), anyInt());
}
@Test
@@ -256,6 +285,14 @@
verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
appWindow.getSurfaceControl(), 60,
Surface.FRAME_RATE_COMPATIBILITY_EXACT, Surface.CHANGE_FRAME_RATE_ALWAYS);
+ if (explicitRefreshRateHints()) {
+ verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+ appWindow.getSurfaceControl(),
+ SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
+ } else {
+ verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+ any(SurfaceControl.class), anyInt());
+ }
}
@Test
@@ -283,6 +320,8 @@
appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
verify(appWindow.getPendingTransaction(), never()).setFrameRate(
any(SurfaceControl.class), anyInt(), anyInt(), anyInt());
+ verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+ any(SurfaceControl.class), anyInt());
}
@Test
@@ -310,5 +349,13 @@
verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
appWindow.getSurfaceControl(), 60,
Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, Surface.CHANGE_FRAME_RATE_ALWAYS);
+ if (explicitRefreshRateHints()) {
+ verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+ appWindow.getSurfaceControl(),
+ SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
+ } else {
+ verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+ any(SurfaceControl.class), anyInt());
+ }
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index ffa1ed9..38a66a9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -19,10 +19,13 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.view.InsetsSource.ID_IME;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_CONSUME_IME_INSETS;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
@@ -432,6 +435,56 @@
}
+ @SetupWindows(addWindows = W_INPUT_METHOD)
+ @Test
+ public void testConsumeImeInsets() {
+ final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
+ final InsetsSource imeSource = new InsetsSource(ID_IME, ime());
+ imeSource.setVisible(true);
+ mImeWindow.mHasSurface = true;
+
+ final WindowState win1 = addWindow(TYPE_APPLICATION, "win1");
+ final WindowState win2 = addWindow(TYPE_APPLICATION, "win2");
+
+ win1.mAboveInsetsState.addSource(imeSource);
+ win1.mHasSurface = true;
+ win2.mAboveInsetsState.addSource(imeSource);
+ win2.mHasSurface = true;
+
+ assertTrue(mImeWindow.isVisible());
+ assertTrue(win1.isVisible());
+ assertTrue(win2.isVisible());
+
+ // Make sure both windows have visible IME insets.
+ assertTrue(win1.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+ assertTrue(win2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+
+ win2.mAttrs.privateFlags |= PRIVATE_FLAG_CONSUME_IME_INSETS;
+
+ displayPolicy.beginPostLayoutPolicyLw();
+ displayPolicy.applyPostLayoutPolicyLw(win2, win2.mAttrs, null, null);
+ displayPolicy.applyPostLayoutPolicyLw(win1, win1.mAttrs, null, null);
+ displayPolicy.finishPostLayoutPolicyLw();
+
+ // Make sure win2 doesn't have visible IME insets, but win1 still does.
+ assertTrue(win2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+ assertFalse(win1.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+ assertTrue(win1.getWindowFrames().hasInsetsChanged());
+
+ win2.mAttrs.privateFlags &= ~PRIVATE_FLAG_CONSUME_IME_INSETS;
+ win2.getWindowFrames().setInsetsChanged(false);
+
+ displayPolicy.beginPostLayoutPolicyLw();
+ displayPolicy.applyPostLayoutPolicyLw(win2, win2.mAttrs, null, null);
+ displayPolicy.applyPostLayoutPolicyLw(win1, win1.mAttrs, null, null);
+ displayPolicy.finishPostLayoutPolicyLw();
+
+ // Make sure both windows have visible IME insets.
+ assertTrue(win1.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+ assertTrue(win2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+ assertTrue(win1.getWindowFrames().hasInsetsChanged());
+ }
+
private WindowState addNavigationBar() {
final Binder owner = new Binder();
final WindowState win = createWindow(null, TYPE_NAVIGATION_BAR, "navBar");
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index 49a8886..c9a83b0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN;
import static android.view.SurfaceControl.RefreshRateRange.FLOAT_TOLERANCE;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -69,15 +70,20 @@
private static final FrameRateVote FRAME_RATE_VOTE_NONE = new FrameRateVote();
private static final FrameRateVote FRAME_RATE_VOTE_DENY_LIST =
- new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+ new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT,
+ FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
private static final FrameRateVote FRAME_RATE_VOTE_LOW_EXACT =
- new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+ new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT,
+ FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
private static final FrameRateVote FRAME_RATE_VOTE_HI_EXACT =
- new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+ new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT,
+ FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
private static final FrameRateVote FRAME_RATE_VOTE_LOW_PREFERRED =
- new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+ new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+ FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
private static final FrameRateVote FRAME_RATE_VOTE_HI_PREFERRED =
- new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+ new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+ FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
// Parcel and Unparcel the LayoutParams in the window state to test the path the object
// travels from the app's process to system server
diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java b/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java
deleted file mode 100644
index c5dd447..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.server.wm.ActivityManagerTestBase.createFullscreenActivityScenarioRule;
-import static android.server.wm.BuildUtils.HW_TIMEOUT_MULTIPLIER;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.app.Activity;
-import android.platform.test.annotations.Presubmit;
-import android.server.wm.CtsWindowInfoUtils;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.TrustedPresentationThresholds;
-
-import androidx.annotation.GuardedBy;
-import androidx.test.ext.junit.rules.ActivityScenarioRule;
-
-import com.android.server.wm.utils.CommonUtils;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-
-import java.util.function.Consumer;
-
-/**
- * TODO (b/287076178): Move these tests to
- * {@link android.view.surfacecontrol.cts.TrustedPresentationCallbackTest} when API is made public
- */
-@Presubmit
-public class TrustedPresentationCallbackTest {
- private static final String TAG = "TrustedPresentationCallbackTest";
- private static final int STABILITY_REQUIREMENT_MS = 500;
- private static final long WAIT_TIME_MS = HW_TIMEOUT_MULTIPLIER * 2000L;
-
- private static final float FRACTION_VISIBLE = 0.1f;
-
- private final Object mResultsLock = new Object();
- @GuardedBy("mResultsLock")
- private boolean mResult;
- @GuardedBy("mResultsLock")
- private boolean mReceivedResults;
-
- @Rule
- public TestName mName = new TestName();
-
- @Rule
- public ActivityScenarioRule<TestActivity> mActivityRule = createFullscreenActivityScenarioRule(
- TestActivity.class);
-
- private TestActivity mActivity;
-
- @Before
- public void setup() {
- mActivityRule.getScenario().onActivity(activity -> mActivity = activity);
- }
-
- @After
- public void tearDown() {
- CommonUtils.waitUntilActivityRemoved(mActivity);
- }
-
- @Test
- public void testAddTrustedPresentationListenerOnWindow() throws InterruptedException {
- TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds(
- 1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS);
- SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- mActivity.getWindow().getRootSurfaceControl().addTrustedPresentationCallback(t, thresholds,
- Runnable::run, inTrustedPresentationState -> {
- synchronized (mResultsLock) {
- mResult = inTrustedPresentationState;
- mReceivedResults = true;
- mResultsLock.notify();
- }
- });
- t.apply();
- synchronized (mResultsLock) {
- assertResults();
- }
- }
-
- @Test
- public void testRemoveTrustedPresentationListenerOnWindow() throws InterruptedException {
- TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds(
- 1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS);
- Consumer<Boolean> trustedPresentationCallback = inTrustedPresentationState -> {
- synchronized (mResultsLock) {
- mResult = inTrustedPresentationState;
- mReceivedResults = true;
- mResultsLock.notify();
- }
- };
- SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- mActivity.getWindow().getRootSurfaceControl().addTrustedPresentationCallback(t, thresholds,
- Runnable::run, trustedPresentationCallback);
- t.apply();
-
- synchronized (mResultsLock) {
- if (!mReceivedResults) {
- mResultsLock.wait(WAIT_TIME_MS);
- }
- assertResults();
- // reset the state
- mReceivedResults = false;
- }
-
- mActivity.getWindow().getRootSurfaceControl().removeTrustedPresentationCallback(t,
- trustedPresentationCallback);
- t.apply();
-
- synchronized (mResultsLock) {
- if (!mReceivedResults) {
- mResultsLock.wait(WAIT_TIME_MS);
- }
- // Ensure we waited the full time and never received a notify on the result from the
- // callback.
- assertFalse("Should never have received a callback", mReceivedResults);
- // results shouldn't have changed.
- assertTrue(mResult);
- }
- }
-
- @GuardedBy("mResultsLock")
- private void assertResults() throws InterruptedException {
- mResultsLock.wait(WAIT_TIME_MS);
-
- if (!mReceivedResults) {
- CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, "test " + mName.getMethodName());
- }
- // Make sure we received the results and not just timed out
- assertTrue("Timed out waiting for results", mReceivedResults);
- assertTrue(mResult);
- }
-
- public static class TestActivity extends Activity {
- }
-}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 6a77b98..7cb2cc3 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9892,7 +9892,6 @@
*
* @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED
* @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT
- * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED
*/
public static final String
KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG =
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index e12a815..b356fde 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -109,10 +109,6 @@
* Then for SDK 35+, if the caller identity is personal profile, then
* {@link #getActiveSubscriptionInfoList} will return subscription 1 only and vice versa.
*
- * <p>If the caller needs to see all subscriptions across user profiles,
- * use {@link #createForAllUserProfiles} to convert the instance to see all. Additional permission
- * may be required as documented on the each API.
- *
*/
@SystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -1815,9 +1811,6 @@
* Then for SDK 35+, if the caller identity is personal profile, then this will return
* subscription 1 only and vice versa.
*
- * <p>If the caller needs to see all subscriptions across user profiles,
- * use {@link #createForAllUserProfiles} to convert this instance to see all.
- *
* <p> The records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by
* {@link SubscriptionInfo#getSubscriptionId}.
*
@@ -2085,9 +2078,6 @@
* Android SDK 35(V) and above, while Android SDK 34(U) and below can see all subscriptions as
* it does today.
*
- * <p>If the caller needs to see all subscriptions across user profiles,
- * use {@link #createForAllUserProfiles} to convert this instance to see all.
- *
* @return The current number of active subscriptions.
*
* @see #getActiveSubscriptionInfoList()
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java
new file mode 100644
index 0000000..068dfe8
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.hoststubgen.nativesubstitution;
+
+import android.os.BadParcelableException;
+import android.os.Parcel;
+
+import java.util.Arrays;
+import java.util.HashMap;
+
+/**
+ * Native implementation substitutions for the LongArrayMultiStateCounter class.
+ */
+public class LongArrayMultiStateCounter_host {
+
+ /**
+ * A reimplementation of {@link com.android.internal.os.LongArrayMultiStateCounter}, only in
+ * Java instead of native. The majority of the code (in C++) can be found in
+ * /frameworks/native/libs/battery/MultiStateCounter.h
+ */
+ private static class LongArrayMultiStateCounterRavenwood {
+ private final int mStateCount;
+ private final int mArrayLength;
+ private int mCurrentState;
+ private long mLastStateChangeTimestampMs = -1;
+ private long mLastUpdateTimestampMs = -1;
+ private boolean mEnabled = true;
+
+ private static class State {
+ private long mTimeInStateSinceUpdate;
+ private long[] mCounter;
+ }
+
+ private final State[] mStates;
+ private final long[] mValues;
+ private final long[] mDelta;
+
+ LongArrayMultiStateCounterRavenwood(int stateCount, int arrayLength) {
+ mStateCount = stateCount;
+ mArrayLength = arrayLength;
+ mStates = new State[stateCount];
+ for (int i = 0; i < mStateCount; i++) {
+ mStates[i] = new State();
+ mStates[i].mCounter = new long[mArrayLength];
+ }
+ mValues = new long[mArrayLength];
+ mDelta = new long[mArrayLength];
+ }
+
+ public void setEnabled(boolean enabled, long timestampMs) {
+ if (enabled == mEnabled) {
+ return;
+ }
+
+ if (!enabled) {
+ setState(mCurrentState, timestampMs);
+ mEnabled = false;
+ } else {
+ if (timestampMs < mLastUpdateTimestampMs) {
+ timestampMs = mLastUpdateTimestampMs;
+ }
+
+ if (mLastStateChangeTimestampMs >= 0) {
+ mLastStateChangeTimestampMs = timestampMs;
+ }
+ mEnabled = true;
+ }
+ }
+
+ public void setState(int state, long timestampMs) {
+ if (mEnabled && mLastStateChangeTimestampMs >= 0 && mLastUpdateTimestampMs >= 0) {
+ if (timestampMs < mLastUpdateTimestampMs) {
+ timestampMs = mLastUpdateTimestampMs;
+ }
+
+ if (timestampMs >= mLastStateChangeTimestampMs) {
+ mStates[mCurrentState].mTimeInStateSinceUpdate +=
+ timestampMs - mLastStateChangeTimestampMs;
+ } else {
+ for (int i = 0; i < mStateCount; i++) {
+ mStates[i].mTimeInStateSinceUpdate = 0;
+ }
+ }
+ }
+ mCurrentState = state;
+ mLastStateChangeTimestampMs = timestampMs;
+ }
+
+ public void updateValue(long[] values, long timestampMs) {
+ if (mEnabled || mLastUpdateTimestampMs < mLastStateChangeTimestampMs) {
+ if (timestampMs < mLastStateChangeTimestampMs) {
+ timestampMs = mLastStateChangeTimestampMs;
+ }
+
+ setState(mCurrentState, timestampMs);
+
+ if (mLastUpdateTimestampMs >= 0) {
+ if (timestampMs > mLastUpdateTimestampMs) {
+ if (delta(mValues, values, mDelta)) {
+ long timeSinceUpdate = timestampMs - mLastUpdateTimestampMs;
+ for (int i = 0; i < mStateCount; i++) {
+ long timeInState = mStates[i].mTimeInStateSinceUpdate;
+ if (timeInState > 0) {
+ add(mStates[i].mCounter, mDelta, timeInState, timeSinceUpdate);
+ mStates[i].mTimeInStateSinceUpdate = 0;
+ }
+ }
+ } else {
+ throw new RuntimeException();
+ }
+ } else if (timestampMs < mLastUpdateTimestampMs) {
+ throw new RuntimeException();
+ }
+ }
+ }
+ System.arraycopy(values, 0, mValues, 0, mArrayLength);
+ mLastUpdateTimestampMs = timestampMs;
+ }
+
+ public void incrementValues(long[] delta, long timestampMs) {
+ long[] values = Arrays.copyOf(mValues, mValues.length);
+ for (int i = 0; i < mArrayLength; i++) {
+ values[i] += delta[i];
+ }
+ updateValue(values, timestampMs);
+ }
+
+ public void getValues(long[] values, int state) {
+ System.arraycopy(mStates[state].mCounter, 0, values, 0, mArrayLength);
+ }
+
+ public void reset() {
+ mLastStateChangeTimestampMs = -1;
+ mLastUpdateTimestampMs = -1;
+ for (int i = 0; i < mStateCount; i++) {
+ mStates[i].mTimeInStateSinceUpdate = 0;
+ Arrays.fill(mStates[i].mCounter, 0);
+ }
+ }
+
+ public void writeToParcel(Parcel parcel) {
+ parcel.writeInt(mStateCount);
+ parcel.writeInt(mArrayLength);
+ for (int i = 0; i < mStateCount; i++) {
+ parcel.writeLongArray(mStates[i].mCounter);
+ }
+ }
+
+ public void initFromParcel(Parcel parcel) {
+ try {
+ for (int i = 0; i < mStateCount; i++) {
+ parcel.readLongArray(mStates[i].mCounter);
+ }
+ } catch (Exception e) {
+ throw new BadParcelableException(e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[");
+ for (int state = 0; state < mStateCount; state++) {
+ if (state != 0) {
+ sb.append(", ");
+ }
+ sb.append(state).append(": {");
+ for (int i = 0; i < mStates[state].mCounter.length; i++) {
+ if (i != 0) {
+ sb.append(", ");
+ }
+ sb.append(mStates[state].mCounter[i]);
+ }
+ sb.append("}");
+ }
+ sb.append("]");
+ if (mLastUpdateTimestampMs >= 0) {
+ sb.append(" updated: ").append(mLastUpdateTimestampMs);
+ }
+ if (mLastStateChangeTimestampMs >= 0) {
+ sb.append(" currentState: ").append(mCurrentState);
+ if (mLastStateChangeTimestampMs > mLastUpdateTimestampMs) {
+ sb.append(" stateChanged: ").append(mLastStateChangeTimestampMs);
+ }
+ } else {
+ sb.append(" currentState: none");
+ }
+ return sb.toString();
+ }
+
+ private boolean delta(long[] values1, long[] values2, long[] delta) {
+ if (delta.length != mArrayLength) {
+ throw new RuntimeException();
+ }
+
+ boolean is_delta_valid = true;
+ for (int i = 0; i < mArrayLength; i++) {
+ if (values2[i] >= values1[i]) {
+ delta[i] = values2[i] - values1[i];
+ } else {
+ delta[i] = 0;
+ is_delta_valid = false;
+ }
+ }
+
+ return is_delta_valid;
+ }
+
+ private void add(long[] counter, long[] delta, long numerator, long denominator) {
+ if (numerator != denominator) {
+ for (int i = 0; i < mArrayLength; i++) {
+ counter[i] += delta[i] * numerator / denominator;
+ }
+ } else {
+ for (int i = 0; i < mArrayLength; i++) {
+ counter[i] += delta[i];
+ }
+ }
+ }
+ }
+
+ public static class LongArrayContainer_host {
+ private static final HashMap<Long, long[]> sInstances = new HashMap<>();
+ private static long sNextId = 1;
+
+ public static long native_init(int arrayLength) {
+ long[] array = new long[arrayLength];
+ long instanceId = sNextId++;
+ sInstances.put(instanceId, array);
+ return instanceId;
+ }
+
+ static long[] getInstance(long instanceId) {
+ return sInstances.get(instanceId);
+ }
+
+ public static void native_setValues(long instanceId, long[] values) {
+ System.arraycopy(values, 0, getInstance(instanceId), 0, values.length);
+ }
+
+ public static void native_getValues(long instanceId, long[] values) {
+ System.arraycopy(getInstance(instanceId), 0, values, 0, values.length);
+ }
+
+ public static boolean native_combineValues(long instanceId, long[] array, int[] indexMap) {
+ long[] values = getInstance(instanceId);
+
+ boolean nonZero = false;
+ Arrays.fill(array, 0);
+
+ for (int i = 0; i < values.length; i++) {
+ int index = indexMap[i];
+ if (index < 0 || index >= array.length) {
+ throw new IndexOutOfBoundsException("Index " + index + " is out of bounds: [0, "
+ + (array.length - 1) + "]");
+ }
+ if (values[i] != 0) {
+ array[index] += values[i];
+ nonZero = true;
+ }
+ }
+ return nonZero;
+ }
+ }
+
+ private static final HashMap<Long, LongArrayMultiStateCounterRavenwood> sInstances =
+ new HashMap<>();
+ private static long sNextId = 1;
+
+ public static long native_init(int stateCount, int arrayLength) {
+ LongArrayMultiStateCounterRavenwood instance = new LongArrayMultiStateCounterRavenwood(
+ stateCount, arrayLength);
+ long instanceId = sNextId++;
+ sInstances.put(instanceId, instance);
+ return instanceId;
+ }
+
+ private static LongArrayMultiStateCounterRavenwood getInstance(long instanceId) {
+ return sInstances.get(instanceId);
+ }
+
+ public static void native_setEnabled(long instanceId, boolean enabled,
+ long timestampMs) {
+ getInstance(instanceId).setEnabled(enabled, timestampMs);
+ }
+
+ public static int native_getStateCount(long instanceId) {
+ return getInstance(instanceId).mStateCount;
+ }
+
+ public static int native_getArrayLength(long instanceId) {
+ return getInstance(instanceId).mArrayLength;
+ }
+
+ public static void native_updateValues(long instanceId, long containerInstanceId,
+ long timestampMs) {
+ getInstance(instanceId).updateValue(
+ LongArrayContainer_host.getInstance(containerInstanceId), timestampMs);
+ }
+
+ public static void native_setState(long instanceId, int state, long timestampMs) {
+ getInstance(instanceId).setState(state, timestampMs);
+ }
+
+ public static void native_incrementValues(long instanceId, long containerInstanceId,
+ long timestampMs) {
+ getInstance(instanceId).incrementValues(
+ LongArrayContainer_host.getInstance(containerInstanceId), timestampMs);
+ }
+
+ public static void native_getCounts(long instanceId, long containerInstanceId, int state) {
+ getInstance(instanceId).getValues(LongArrayContainer_host.getInstance(containerInstanceId),
+ state);
+ }
+
+ public static void native_reset(long instanceId) {
+ getInstance(instanceId).reset();
+ }
+
+ public static void native_writeToParcel(long instanceId, Parcel parcel, int flags) {
+ getInstance(instanceId).writeToParcel(parcel);
+ }
+
+ public static long native_initFromParcel(Parcel parcel) {
+ int stateCount = parcel.readInt();
+ if (stateCount < 0 || stateCount > 0xEFFF) {
+ throw new BadParcelableException("stateCount out of range");
+ }
+ // LongArrayMultiStateCounter.cpp uses AParcel, which throws on out-of-data.
+ if (parcel.dataPosition() >= parcel.dataSize()) {
+ throw new RuntimeException("Bad parcel");
+ }
+ int arrayLength = parcel.readInt();
+ if (parcel.dataPosition() >= parcel.dataSize()) {
+ throw new RuntimeException("Bad parcel");
+ }
+ long instanceId = native_init(stateCount, arrayLength);
+ getInstance(instanceId).initFromParcel(parcel);
+ if (parcel.dataPosition() > parcel.dataSize()) {
+ throw new RuntimeException("Bad parcel");
+ }
+ return instanceId;
+ }
+
+ public static String native_toString(long instanceId) {
+ return getInstance(instanceId).toString();
+ }
+}