Merge "Reduce handler message spam." into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 08961ed..22f736e 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}",
@@ -741,6 +742,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 bd59a3b..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(
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 cbba420..d490c3f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4468,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);
@@ -4510,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);
@@ -4558,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);
@@ -9839,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();
@@ -9855,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);
@@ -53881,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";
@@ -60864,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 46d7d76..9a65388 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -4009,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";
@@ -4115,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 {
@@ -10927,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);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index eaabda8..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();
}
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/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/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/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/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/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/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/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 f7a33e0..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;
@@ -5905,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/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/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/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/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/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/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/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 1fbbb6e..aaddf0e 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -607,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",
@@ -973,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",
@@ -1345,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",
@@ -2827,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",
@@ -3007,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",
@@ -3085,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",
@@ -3313,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",
@@ -3607,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",
@@ -3739,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",
@@ -3877,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",
@@ -3925,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",
@@ -4225,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",
@@ -4399,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",
@@ -4683,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/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 eb30119..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,6 +48,7 @@
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.DesktopTasksController;
@@ -901,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/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/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/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/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/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/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/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/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/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 e88a975..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
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 d9697db..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> =
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/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/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index ffbc6ee..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,7 +177,7 @@
}
.stateIn(
scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
+ started = SharingStarted.Lazily,
initialValue = NotificationContainerBounds(),
)
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/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/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/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/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b992e7714..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();
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/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 09107c1..2d145c2 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();
}
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..4c4dafa 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);
+ sessionState.inputId, 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
@@ -3110,8 +3119,22 @@
@GuardedBy("mLock")
private void logExternalInputEvent(int eventType, String inputId, SessionState sessionState) {
UserState userState = getOrCreateUserStateLocked(sessionState.userId);
- TvInputState tvInputState = userState.inputMap.get(inputId);
+ // Try finding the actual input id in inputMap. If not found, find the input id as is.
+ String inputIdForLogging = getSessionActualInputId(sessionState);
+ TvInputState tvInputState = userState.inputMap.get(inputIdForLogging);
+ if (tvInputState == null) {
+ inputIdForLogging = inputId;
+ tvInputState = userState.inputMap.get(inputIdForLogging);
+ }
+ if (tvInputState == null) {
+ Slog.w(TAG, "Cannot find input state for input id " + inputIdForLogging);
+ return;
+ }
TvInputInfo tvInputInfo = tvInputState.info;
+ if (tvInputInfo == null) {
+ Slog.w(TAG, "TvInputInfo is null for input id " + inputIdForLogging);
+ 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);
+ mSessionState.inputId, mSessionState);
}
- mOnScreenInputId = actualInputId;
+ mOnScreenInputId = sessionActualInputId;
mOnScreenSessionState = mSessionState;
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4cc1c0d..1861b2e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -546,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 */
@@ -1194,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());
@@ -3265,6 +3273,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();
@@ -3303,6 +3317,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/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/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 c7a7e28..9c4ad5c 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1195,6 +1195,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
@@ -2401,6 +2406,9 @@
}
mWmService.postWindowRemoveCleanupLocked(this);
+
+ mWmService.mTrustedPresentationListenerController.removeIgnoredWindowTokens(
+ getWindowToken());
}
@Override
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/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/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/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/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();
+ }
+}